Building IBM i program calls with PCML

To build IBM® i program calls with PCML, you must start by creating a Java™ application and a PCML source file.

Depending on your design process, you must write one or more PCML source files where you describe the interfaces to the IBM i programs that will be called by your Java application. Refer to PCML syntax for a detailed description of the language.

Then your Java application interacts with the PCML classes (in this case, the ProgramCallDocument class). The ProgramCallDocument class uses your PCML source file to pass information between your Java application and the IBM i programs. Figure 1 illustrates how Java applications interact with the PCML classes.

Figure 1. Making program calls to the server using PCML.

Flow chart that outlines the process of creating a program call with PCML Image description

When your application constructs the ProgramCallDocument object, the XML parser reads and parses the PCML source file. For more information about using an XML parser with IBM Toolbox for Java, see XML parser and XSLT processor.

After the ProgramCallDocument class has been created, the application program uses the ProgramCallDocument class's methods to retrieve the necessary information from the server through the IBM i distributed program call (DPC) server.

To improve run-time performance, the ProgramCallDocument class can be serialized during your product build time. The ProgramCallDocument is then constructed using the serialized file. In this case, the XML parser is not used at run-time. Refer to Using serialized PCML files.

Using PCML source files

Your Java application uses PCML by constructing a ProgramCallDocument object with a reference to the PCML source file. The ProgramCallDocument object considers the PCML source file to be a Java resource. The java application finds the PCML source file by using the Java CLASSPATH

The following Java code constructs a ProgramCallDocument object:

    AS400 as400 = new AS400();
    ProgramCallDocument pcmlDoc = new ProgramCallDocument(as400, "myPcmlDoc");

The ProgramCallDocument object will look for your PCML source in a file called myPcmlDoc.pcml. Notice that the .pcml extension is not specified on the constructor.

If you are developing a Java application in a Java package, you can package-qualify the name of the PCML resource:

    AS400 as400 = new AS400();
    ProgramCallDocument pcmlDoc = new ProgramCallDocument(as400, "com.company.package.myPcmlDoc");

Using serialized PCML files

To improve run-time performance, you can use a serialized PCML file. A serialized PCML file contains serialized Java objects representing the PCML. The objects that are serialized are the same objects that are created when you construct the ProgramCallDocument from a source file as described above.

Using serialized PCML files improves performance because the XML parser is not needed at run-time to process the PCML tags.

The PCML can be serialized using either of the following methods:

  • From the command line:
       java com.ibm.as400.data.ProgramCallDocument -serialize mypcml

    This method is helpful for having batch processes to build your application.

  • From within a Java program:
        ProgramCallDocument pcmlDoc; // Initialized elsewhere
        pcmlDoc.serialize();

If your PCML is in a source file named myDoc.pcml, the result of serialization is a file named myDoc.pcml.ser.

PCML source files vs. serialized PCML files

Consider the following code to construct a ProgramCallDocument:

    AS400 as400 = new AS400();
    ProgramCallDocument pcmlDoc = new ProgramCallDocument(as400, "com.mycompany.mypackage.myPcmlDoc");

The ProgramCallDocument constructor will first try to find a serialized PCML file named myPcmlDoc.pcml.ser in the com.mycompany.mypackage package in the Java CLASSPATH. If a serialized PCML file does not exist, the constructor will then try to find a PCML source file named myPcmlDoc.pcml in the com.mycompany.mypackage package in the Java CLASSPATH. If a PCML source file does not exist, an exception is thrown.

Qualified names

Your Java application uses ProgramCallDocument.setValue() to set input values for the IBM i program being called. Likewise, your application uses ProgramCallDocument.getValue() to retrieve output values from the IBM i program.

When accessing values from the ProgramCallDocument class, you must specify the fully qualified name of the document element or <data> tag. The qualified name is a concatenation of the names of all the containing tags with each name separated by a period.

For example, given the following PCML source, the qualified name for the "nbrPolygons" item is "polytest.parm1.nbrPolygons". The qualified name for accessing the "x" value for one of the points in one of the polygons is "polytest.parm1.polygon.point.x".

If any one of the elements needed to make the qualified name is unnamed, all descendants of that element do not have a qualified name. Any elements that do not have a qualified name cannot be accessed from your Java program.

<pcml version="1.0">
  <program name="polytest" path="/QSYS.lib/MYLIB.lib/POLYTEST.pgm">
    <!-- Parameter 1 contains a count of polygons along with an array of polygons -->
    <struct name="parm1" usage="inputoutput">
      <data name="nbrPolygons" type="int" length="4" init="5" />
      <!-- Each polygon contains a count of the number of points along with an array of points -->
      <struct name="polygon" count="nbrPolygons">
        <data name="nbrPoints" type="int" length="4" init="3" />
        <struct name="point" count="nbrPoints" >
          <data name="x" type="int" length="4" init="100" />
          <data name="y" type="int" length="4" init="200" />
        </struct>
      </struct>
    </struct>
  </program>
</pcml>

Accessing data in arrays

Any <data> or <struct> element can be defined as an array using the count attribute. Or, a <data> or <struct> element can be contained within another <struct> element that is defined as an array.

Furthermore, a <data> or <struct> element can be in a multidimensional array if more than one containing element has a count attribute specified.

In order for your application to set or get values defined as an array or defined within an array, you must specify the array index for each dimension of the array. The array indices are passed as an array of int values. Given the source for the array of polygons shown above, the following Java code can be used to retrieve the information about the polygons:

    ProgramCallDocument polytest; // Initialized elsewhere
    Integer nbrPolygons, nbrPoints, pointX, pointY;
    nbrPolygons = (Integer) polytest.getValue("polytest.parm1.nbrPolygons");
    System.out.println("Number of polygons:" + nbrPolygons);
    indices = new int[2];
    for (int polygon = 0; polygon < nbrPolygons.intValue(); polygon++) 
    {
        indices[0] = polygon;
        nbrPoints = (Integer) polytest.getValue("polytest.parm1.polygon.nbrPoints", indices );
        System.out.println("  Number of points:" + nbrPoints);

        for (int point = 0; point < nbrPoints.intValue(); point++) 
        {
            indices[1] = point;
            pointX = (Integer) polytest.getValue("polytest.parm1.polygon.point.x", indices );
            pointY = (Integer) polytest.getValue("polytest.parm1.polygon.point.y", indices );
            System.out.println("    X:" + pointX + " Y:" + pointY);
        }
    }

Debugging

When you use PCML to call programs with complex data structures, it is easy to have errors in your PCML that result in exceptions from the ProgramCallDocument class. If the errors are related to incorrectly describing offsets and lengths of data, the exceptions can be difficult to debug.

Use the following method from the Trace class to turn on PCML tracing:

     Trace.setTraceOn(true);      // Turn on tracing function.
     Trace.setTracePCMLOn(true);  // Turn on PCML tracing.
Note: All public methods in the PcmlMessageLog class, including tracing, were deprecated in V5R2.

The Trace setFileName() method enables you to send the following types of information to specific log files or, by default, to System.out:

  • A dump of the hexadecimal data being transferred between the Java application and the IBM i program. This shows the program input parameters after character data is converted to EBCDIC and integers are converted to big-endian. It also shows the output parameters before they are converted to the Java environment.

    The data is shown in a typical hexadecimal dump format with hexadecimal digits on the left and a character interpretation on the right. The following is an example of this dump format. (The following example was altered to allow for width restrictions)

    qgyolobj[6]
    Offset : 0....... 4....... 8....... C....... 0....... 4....... 8....... C.......   
             0...4...8...C...0...4...8...C...
         0 : 5CE4E2D9 D7D9C640 4040                                                   
             **USRPRF                         *
    In the above example, the dump shows the seventh parameter has 10 bytes of data set to "*USRPRF ".
  • For output parameters, following the hexadecimal dump is a description of how the data has been interpreted for the document. (The following example was altered to allow for width restrictions)
/QSYS.lib/QGY.lib/QGYOLOBJ.pgm[2]
Offset : 0....... 4....... 8....... C....... 0....... 4....... 8....... C.......   
         0...4...8...C...0...4...8...C...
     0 : 0000000A 0000000A 00000001 00000068 D7F0F9F9 F0F1F1F5 F1F4F2F6 F2F5F400  
         *................P09901151426254.*
    20 : 00000410 00000001 00000000 00000000 00000000 00000000 00000000 00000000  
         *................................*
    40 : 00000000 00000000 00000000 00000000                                      
         *................                *
Reading data -- Offset: 0   Length: 4   Name: "qgyolobj.listInfo.totalRcds"   
 Byte data: 0000000A
Reading data -- Offset: 4   Length: 4   Name: "qgyolobj.listInfo.rcdsReturned"    
 Byte data: 0000000A
Reading data -- Offset: 8   Length: 4   Name: "qgyolobj.listInfo.rqsHandle"   
 Byte data: 00000001
Reading data -- Offset: c   Length: 4   Name: "qgyolobj.listInfo.rcdLength"    
 Byte data: 00000068
Reading data -- Offset: 10  Length: 1   Name: "qgyolobj.listInfo.infoComplete"    
 Byte data: D7
Reading data -- Offset: 11  Length: 7   Name: "qgyolobj.listInfo.dateCreated" 
 Byte data: F0F9F9F0F1F1F5
Reading data -- Offset: 18  Length: 6   Name: "qgyolobj.listInfo.timeCreated" 
 Byte data: F1F4F2F6F2F5
Reading data -- Offset: 1e  Length: 1   Name: "qgyolobj.listInfo.listStatus"  
 Byte data: F4
Reading data -- Offset: 1f  Length: 1   Name: "qgyolobj.listInfo.[8]" 
 Byte data: 00
Reading data -- Offset: 20  Length: 4   Name: "qgyolobj.listInfo.lengthOfInfo"    
 Byte data: 00000410
Reading data -- Offset: 24  Length: 4   Name: "qgyolobj.listInfo.firstRecord" 
 Byte data: 00000001
Reading data -- Offset: 28  Length: 40  Name: "qgyolobj.listInfo.[11]"    
 Byte data: 00000000000000000000000000000000000000000000000000000000000000000000000000000000
The above messages can be very helpful in diagnosing cases where the output data coming from the IBM i program does not match the PCML source. This can easily occur when you are using dynamic lengths and offsets.