Java and External Data Structures on IBM i

One of my favorite techniques for passing structured bulk data around is using datas queue’s with the format of the data defined in an external data structure.

In RPG this is very easy … you defined a data structure using the EXTNAME keyword.

dcl-ds stuct1 extname('EXTDS1') end-ds;  

Then you just use the data structure name when calling the QRCVDTAQ or QSNDDTAQ api’s and the data will be nicely mapped into the appropriate structure field.

But what if you wanted to allow a Java application to consume or populate the data queue?

The first issue is the fact that Java uses unicode and the native encoding on IBM i is EBCDIC. You will have to deal with data conversion.

The next issue is the layout of the data queue entry itself. How do you get the entry broken down into the defined fields?

The solution is to use JT400 Record Formats.

The RecordFormat class allows you to take host data and parse it into a meaningful structure and convert fields into the appropriate Java data types.

For the purposes of this article, our external data structure will be defined like this…

create table EXTDS1 (
   field1 char (10), 
   field2 char (10), 
   field3 char (10), 
   field4 dec (6,2)
); 

The external data structure file can be created with DDS or DDL. You’ll need to ensure that the user profile running the java code has authority to read the file. Because the file is only being used as an external data structure format, it should never have any data in it.

Read & parse a data queue entry

Here’s some example java code that retrieves an external description then reads & parses a data queue entry…

// establish a connection to the host
AS400 host = new AS400(HOSTNAME, USERID, PASSWORD);
		
// load the description of TABLE1 
String dsPath = QSYSObjectPathName.toPath("YOURLIB", "EXTDS1", "FILE");
		
AS400FileRecordDescription recordDescription = new AS400FileRecordDescription(
		host, dsPath);

// get the record formats for the table. Generally there will only
// be one format, so we can grab the first element from the array.
RecordFormat[] formats = recordDescription.retrieveRecordFormat();
RecordFormat format = formats[0];
		
// read a record from the data queue
String dqPath = QSYSObjectPathName.toPath("YOURLIB", "DQNAME", "DTAQ");
		
DataQueue dq = new DataQueue(host, dqPath);
		
DataQueueEntry entry = dq.read();

// get a byte array of the data queue entry's content
byte[] entryBytes = entry.getData();
		
// create a new record from the format, using the data queue entry
// contents as the data
Record rec = format.getNewRecord(entryBytes);

// now we can access the fields using field names from the 
// external data structure

// FIELD1 is a simple character field, so we can convert it to a 
// String.
String field1 = (String)rec.getField("FIELD1");

// FIELD4 is a decimal field, so it gets converted to BigDecimal
BigDecimal field4 = (BigDecimal) rec.getField("FIELD4");

You may be wondering why we’re using the getData() method on the DataQueueEntry instead of getString(). The reason for this is that we don’t want the decimal field to be converted from EBCDIC to Unicode because it may.be a packed decimal field in the external data structure definition.

Format and send a data queue entry

Sending a data queue entry from Java is pretty easy too…

// establish a connection to the host
AS400 host = new AS400(HOSTNAME, USERID, PASSWORD);
		
// load the description of TABLE1 
String dsPath = QSYSObjectPathName.toPath("YOURLIB", "EXTDS1", "FILE");
		
// get the record formats for the table. Generally there will only
// be one format, so we can grab the first element from the array.
AS400FileRecordDescription recordDescription = new AS400FileRecordDescription(
		host, dsPath);

RecordFormat[] formats = recordDescription.retrieveRecordFormat();
RecordFormat format = formats[0];

// create an empty record from the record format
Record rec = format.getNewRecord();

// populate the fields in the record using field names from the 
// external data structure
rec.setField("FIELD1", "CONTENTS1");
rec.setField("FIELD2", "CONTENTS1");
rec.setField("FIELD3", "CONTENTS1");
rec.setField("FIELD4", new BigDecimal("123.45"));
		
// get a byte array of the record conetns
byte[] bytes = rec.getContents();

// and send it to the data queue
String dqPath = QSYSObjectPathName.toPath("YOURLIB", "DQNAME", "DTAQ");
DataQueue dq = new DataQueue(host, dqPath);
		
dq.write(bytes);

Obviously the code above is just a demonstration.

I would suggest that, instead of loading the record format’s each time you want to process a data queue entry, you load the formats ahead of time and cache them or hold them in static memory. You will have to be sure to reload the record format’s whenever the external data structure format changes.

Another thing to keep in mind, the RecordFormat class in JT400 can’t handle all data types that can exist in a file. Examples of this are: BINARY, DATALINK, CLOB, & DECFLOAT.

Leave a Reply

Your email address will not be published. Required fields are marked *