Java Native Methods and Legacy Databases

Native-method libraries extend Java into new areas of support not yet developed in pure Java. In this article, our authors use a native-method library with Java to access the FairCom database engine.


August 01, 1997
URL:http://www.drdobbs.com/jvm/java-native-methods-and-legacy-databases/184410395

Dr. Dobb's Sourcebook July/August 1997: Java Native Methods and Legacy Databases

Yoshiki is director of FairCom Japan and the primary Java developer for FairCom. Mark is a support engineer and technical writer for FairCom U.S. Marco is a support engineer for FairCom Europe. They can be contacted at http://www.faircom.com.


In most Java circles, the mention of native-method libraries brings frowns. However, native-method libraries extend Java into new areas of support not yet developed in pure Java. In this article, we will use a native-method library with Java to access a database server. We'll do this using the FairCom Server as the database server, and the j-tree API overlaying the c-tree Plus database API. FairCom's c-tree Plus and FairCom Server are ISAM APIs used internally in many RDBMS applications, in time-critical controlling software, and when standard two-dimensional relational databases won't do. The c-tree family has been ported to over 100 platforms, ranging from Cray supercomputers to embedded machines.

Not every platform provides a Java interpreter. This leads to an important migration issue. As Java's features become more tempting, and more systems change over, the need to access data on platforms that do not currently support Java grows. Using a native-language DLL gives Java access to legacy databases and to data on unsupported platforms. This technique provides an incremental migration path from native-language applications to a pure Java environment.

Creating a client with Java provides many advantages including multithreading, distributed processing, and platform-independent user interfaces. Threaded applications are quickly becoming the standard, and Java's support of threading complements the FairCom Server, allowing the application to keep multiple threads of activity running concurrently.

As a "write-once, run-anywhere" language, Java naturally allows distributed processing, while maintaining server control of updates to client software. The host changes the user interface at the host system, at the host's convenience, while shifting the processing load to the client. A Java client runs anywhere Java support exists, even as an applet in a browser.

The j-tree Native-Language API

The advantages of a native-language API include access to legacy data and access to servers on platforms not supporting Java. The primary disadvantage is that the C library must be compiled for the client's platform and is not automatically loaded by the client's browser.

Our example uses Java with FairCom's j-tree API to access a c-tree Plus database via FairCom Server, and assumes some knowledge of the c-tree Plus API. Currently, j-tree is JDK 1.0.2 compliant. In February 1997, JDK 1.1 added Java Native Interface (JNI), JDBC, and Remote Method Invocation (RMI). FairCom is examining all three for a future release as the technology matures. Check http://www.faircom.com/ for the latest information on j-tree.

j-tree uses the c-tree Plus client API to access standard c-tree Plus databases via FairCom Servers. Using c-tree Plus client communications, j-tree provides access to any FairCom Server running TCP/IP on any platform, like a standard C-language c-tree Plus client. Java clients work side-by-side with native-language clients on the same databases. It doesn't matter where the database and the FairCom Server reside, even if the server platform has no native Java support.

Listing One lists the methods implemented in ctree.class. Users of c-tree Plus will recognize the short names of c-tree Plus API functions. Only a subset of c-tree Plus functions has been implemented. j-tree merges similar c-tree Plus functions into one ISAM-level API.

The Sample Application

Listing Two is a simple contact database. It takes new contacts, lists contacts, deletes old contacts, and updates existing data. We have limited ourselves to a console-style interface to focus on the database calls. The opening portion of the class defines several constants.

main() creates ct, an instance of the ctree class, then creates the data and ISAM file information structures for the application. ct is initialized (ct.INTISAMX), files are opened, the work loop is executed, then the application is shut down. CtINTISAMX connects the client to the server referenced in serverName.

The application contacts a FairCom Server running on the same machine by default. By changing serverName, userID, and userPassword, you can access servers on other machines and take advantage of password security.

OpenFiles() tries to open the necessary files using OPNRFILX. If that fails, CREIFILX tries to create then reopen the files with OPNRFILX, which uses the ISAM information stored in the existing file. CREIFILX creates new data and index files using the ISAM information stored in ifilInfo[].

WorkLoop() contains the user interface. The option to add, show, delete, or update records is given, and appropriate methods are called.

AddARecord() calls methods to build a data record, begins a transaction using TRANBEG, and adds the record. If ADDREC fails, TRANABT aborts the transaction to remove any partial updates. A successful ADDREC is committed to the file with TRANEND. GetInput() and GetAField() break down the repetitious process of entering individual data fields.

ShowRecords() retrieves the first record in the Customer Number index using FRSREC. The user can then delete or edit the current record, retrieve the next record using NXTREC, or quit back to the main menu.

DeleteCurrent() is called from the main menu and from ShowRecords(). It simply deletes the current record using DELREC bracketed by transactions, as in AddARecord(). UpdateCurrent() is similar to AddARecord, receiving new information from the user and rewriting the record using RWTREC.

The main menu calls SelectARecord() before updates and deletes. It allows users to enter a Customer Number, then retrieves the record with the matching or next-higher number using GTEREC.

DisplayARecord() simply displays the fields in dataField with a little formatting. isam_error translates error constants into readable error messages. The two finalize() procedures handle hard and soft shutdowns, including closing files using CLRFIL and informing the server that users have finished by calling STPUSR.

Conclusion

As a user interface, Java's transportability -- when combined with a native-language DLL accessing heterogeneous database servers -- gives broad access to data. While the DLL must be acquired separately, the provided flexibility is worth the effort. Once the client has the DLL, updates to the user interface have all the advantages of Java flexibility, while maintaining the transportability of data provided by the DLL. This provides an intermediate step between pure C/C++ and pure Java, maintaining access to data as your clients migrate to a new interface.

DDJ

Listing One

public synchronized native int  INTISAMX(String regid,String svn,                                           String uid, String psw);
public synchronized native int  CREIFILX(String regid,String[] isamdata,
                                           String[] dodadata);
public synchronized native int  OPNRFILX(String regid,int filno,
                                           String path,int filmode);
public synchronized native int  FRSREC(String regid,int keyno,
                                          String[] recbuf);
public synchronized native int  GTEREC(String regid,int keyno,
                                          Object[] keyval,String[] recbuf);
public synchronized native int  LTEREC(String regid,int keyno,
                                          Object[] keyval,String[] recbuf);
public synchronized native int  NXTREC(String regid,int keyno,
                                                  String[] recbuf);
public synchronized native int  PRVREC(String regid,int keyno,
                                                  String[] recbuf);
public synchronized native int  LSTREC(String regid,int keyno,
                                                  String[] recbuf);
public synchronized native int  ADDREC(String regid,int datno,
                                                  String[] recbuf);
public synchronized native int  RWTREC(String regid,int datno,
                                                  String[] recbuf);
public synchronized native int  DELREC(String regid,int datno);
public synchronized native int  BATGET(String regid,int keyno,
                   Object[] keyval,String[][] recbuf,int siglen,int numrec);
public synchronized native int  BATGET_NEXT(String regid,int keyno,
                                            String[][] recbuf,int numrec);
public synchronized native int  TRANBEG(String regid);
public synchronized native int  TRANABT(String regid);
public synchronized native int  TRANEND(String regid);
public synchronized native int  CLRFIL(String regid,int datno);
public synchronized native int  STPUSR(String regid);


Back to Article

Listing Two

/*                                                                          * *  contacts.java   A simple contact manager application                    *
 *                                                                          */
 
import java.io.*;
public class Contacts 
{
    static final int    NO_ERROR=0;
    static final int    DLOK_ERR=42;
    static final int    INOT_ERR=101;
    static final int    FNOP_ERR=12;
    static final int    KOPN_ERR=18;
    static final int    DOPN_ERR=19;
    static final int    KDUP_ERR=2;


static final int ROLODAT=0; static final int NAMEIDX=1; static final int CUSTIDX=2; static final int NUMFLD=14; static final int INPBUFSIZ=300;

/**************************************************************************** * MAIN The primary method of the application. *****************************************************************************/ public static void main (String arg[]) { ctree ct = null; int returnValue = 0; int dataFileNumber = -1;

String instanceID = "contacts"; String userID = null; String userPassword = null; String serverName = "FAIRCOMS@localhost";

String dataField[] = new String[NUMFLD]; String ifilInfo[] = new String[6]; String dodaInfo[] = new String[NUMFLD+1];

System.out.println(); System.out.println("CONTACTS: j-tree sample application"); System.out.println();

for (int i = 0; i < NUMFLD; i++) dataField[i] = "";

/* IFIL info - pfilnam dreclen dxtdsiz dfilmod * dnumidx ixtdsiz ifilmod rfstfld rlstfld */ ifilInfo[0] = "rolodex 300 4096 0 2 4096 0 NULL NULL"; /* IIDX info - ikeylen ikeytyp ikeydup inulkey iempchr * inumseg ridxnam aidxnam altseq */ ifilInfo[1] = " 22 12 1 1 0 2 Nameidx NULL NULL"; /* ISEG info - soffset slength segmod */ ifilInfo[2] = " 19 10 2"; ifilInfo[3] = " 4 8 2"; /* IIDX info - ikeylen ikeytyp ikeydup inulkey iempchr * inumseg ridxnam aidxnam altseq */ ifilInfo[4] = " 2 0 0 1 0 1 Custidx NULL NULL"; /* ISEG info - soffset slength segmod */ ifilInfo[5] = " 2 2 8"; /* DODA info - Symbol Name Type Length */ dodaInfo[0] = "Del_Flag CT_INT2 2"; dodaInfo[1] = "Cust_Num CT_INT2 2"; dodaInfo[2] = "FirstName CT_FSTRING 15"; dodaInfo[3] = "LastName CT_FSTRING 25"; dodaInfo[4] = "Address1 CT_FSTRING 40"; dodaInfo[5] = "Address2 CT_FSTRING 40"; dodaInfo[6] = "City CT_FSTRING 15"; dodaInfo[7] = "State CT_FSTRING 3"; dodaInfo[8] = "Zip CT_FSTRING 11"; dodaInfo[9] = "Phone CT_FSTRING 15"; dodaInfo[10] = "Fax CT_FSTRING 15"; dodaInfo[11] = "EMail CT_FSTRING 30"; dodaInfo[12] = "WebSite CT_FSTRING 40"; dodaInfo[13] = "Padding CT_FSTRING 47"; dodaInfo[14] = "END"; try { ct = new ctree(); } catch (ArrayIndexOutOfBoundsException e) { System.err.println("Fatal Error! Could not create c-tree class instance."); System.exit(-1); } if ((returnValue=ct.INTISAMX(instanceID, serverName, userID, userPassword)) != NO_ERROR) finalize(ct, instanceID, "Cannot initialize c-tree", returnValue); dataFileNumber = OpenFiles(ct, instanceID, ifilInfo, dodaInfo); WorkLoop(ct, instanceID, dataField, dataFileNumber); finalize(ct, instanceID, dataFileNumber); } public static int OpenFiles(ctree ct, String instanceID, String[] ifilInfo, String[] dodaInfo) { int dataFileNumber = -1; int returnValue = 0; if ((dataFileNumber = ct.OPNRFILX(instanceID, -1, "rolodex.dat", 0)) < 0) { if ((dataFileNumber = ct.CREIFILX(instanceID, ifilInfo, dodaInfo)) < 0) finalize(ct, instanceID, "Cannot open or create inventory files", dataFileNumber); else { if ((dataFileNumber = ct.OPNRFILX(instanceID, -1, "rolodex.dat", 0)) < 0) finalize(ct, instanceID, "Cannot open inventory files", dataFileNumber); System.out.println(); System.out.print("Successfully created data files and indices"); } } else { System.out.println(); System.out.print("Successfully opened data files and indices"); } return(dataFileNumber); } public static void WorkLoop(ctree ct, String instanceID, String[] dataField, int dataFileNumber) { int keyno = dataFileNumber + CUSTIDX; byte choice[] = new byte[2]; choice[0] = '\0'; while (choice[0] != 'Q' && choice[0] != 'q') { System.out.println(""); System.out.println(""); System.out.print("A)dd D)elete S)how U)pdate Q)uit: "); System.out.flush(); try { System.in.read(choice); } catch (IOException ioe) { System.err.println(ioe.toString()); ioe.printStackTrace(); } switch(choice[0]) { case 'a': case 'A': AddARecord(ct, instanceID, dataField, dataFileNumber); break;

case 's': case 'S': ShowRecords(ct, instanceID, dataField, dataFileNumber); break; case 'd': case 'D': if (SelectARecord(ct, instanceID, dataField, dataFileNumber)) DeleteCurrent(ct, instanceID, dataField, dataFileNumber); break; case 'u': case 'U': if (SelectARecord(ct, instanceID, dataField, dataFileNumber)) UpdateCurrent(ct, instanceID, dataField, dataFileNumber); break; default: break; } } } public static void AddARecord(ctree ct, String instanceID, String[] dataField, int dataFileNumber) { int returnValue = 0;

System.out.println(); System.out.println("ADD NEW DATA"); System.out.println();

for (int i = 0; i < NUMFLD; i++) dataField[i] = "";

GetInput(dataField);

if ((returnValue = ct.TRANBEG(instanceID)) == 0) isam_error(returnValue); else if ((returnValue = ct.ADDREC(instanceID, dataFileNumber, dataField)) != 0) { System.out.print("Error adding record: " + returnValue); ct.TRANABT(instanceID); } else { System.out.println(); System.out.print("Successful Addition."); ct.TRANEND(instanceID); } } public static void GetInput(String[] dataField) { dataField[1] = GetAField("Customer Number : ", dataField[1]); dataField[2] = GetAField("First Name : ", dataField[2]); dataField[3] = GetAField("Last Name : ", dataField[3]); dataField[4] = GetAField("Address (Line 1): ", dataField[4]); dataField[5] = GetAField("Address (Line 2): ", dataField[5]); dataField[6] = GetAField("City : ", dataField[6]); dataField[7] = GetAField("State : ", dataField[7]); dataField[8] = GetAField("Zip Code : ", dataField[8]); dataField[9] = GetAField("Phone Number : ", dataField[9]); dataField[10] = GetAField("Fax Number : ", dataField[10]); dataField[11] = GetAField("eMail address : ", dataField[11]); dataField[12] = GetAField("Web Site URL : ", dataField[12]); } public static String GetAField(String Prompt, String Target) { byte inpbuf[] = new byte[INPBUFSIZ];

System.out.println();

System.out.println(" Current " + Prompt + " " + Target); System.out.print("Enter new " + Prompt); System.out.flush(); try { System.in.read(inpbuf); } catch (IOException ioe) { System.err.println(ioe.toString()); ioe.printStackTrace(); } String buffer = new String(inpbuf,0); return (buffer.trim()); } public static void ShowRecords(ctree ct, String instanceID, String[] dataField, int dataFileNumber) { int returnValue = 0; int keyno = dataFileNumber + CUSTIDX; byte choice[] = new byte[2];

choice[0] = '\0';

if ((returnValue = ct.FRSREC(instanceID, keyno, dataField)) != NO_ERROR) { System.out.println("Error reading first record"); isam_error(returnValue); } else { while (choice[0] != 'Q' && choice[0] != 'q') { System.out.println(); System.out.println(); System.out.println("The current record is:"); DisplayARecord(dataField); System.out.println(); System.out.println(); System.out.print("D)elete E)dit N)ext Q)uit : "); System.out.flush(); try { System.in.read(choice); } catch (IOException ioe) { System.err.println(ioe.toString()); ioe.printStackTrace(); } switch(choice[0]) { case 'd': case 'D': DeleteCurrent(ct, instanceID, dataField, dataFileNumber); case 'n': case 'N': if ((returnValue = ct.NXTREC(instanceID, keyno, dataField)) != NO_ERROR) { if (returnValue == INOT_ERR) { System.out.print("Last record in index."); choice[0] = 'q'; } else finalize(ct, instanceID, "Error reading next record.", returnValue); } break; case 'e': case 'E': UpdateCurrent(ct, instanceID, dataField, dataFileNumber); break; default: break; } } } } public static void DeleteCurrent(ctree ct, String instanceID, String[] dataField, int dataFileNumber) { int returnValue = 0; byte choice[] = new byte[2]; System.out.println(); System.out.print("Delete this record (Y/n)?"); System.out.flush(); try { System.in.read(choice); } catch (IOException ioe) { System.err.println(ioe.toString()); ioe.printStackTrace(); } if (choice[0] == 'n' || choice[0] == 'N') return; if ((returnValue = ct.TRANBEG(instanceID)) == 0) isam_error(returnValue); else if ((returnValue = ct.DELREC(instanceID,dataFileNumber)) != 0) { System.out.println(); System.out.println(); System.out.println("Delete failed (code " + returnValue + ")."); ct.TRANABT(instanceID); } else { System.out.println(); System.out.println("The following record was deleted: "); DisplayARecord(dataField); ct.TRANEND(instanceID); } } public static void UpdateCurrent(ctree ct, String instanceID, String[] dataField, int dataFileNumber) { int returnValue = 0; byte choice[] = new byte[2];

System.out.println(); System.out.print("Edit this record (Y/n)?"); System.out.flush(); try { System.in.read(choice); } catch (IOException ioe) { System.err.println(ioe.toString()); ioe.printStackTrace(); } if (choice[0] == 'n' || choice[0] == 'N') return; GetInput(dataField); if ((returnValue = ct.TRANBEG(instanceID)) == 0) { isam_error(returnValue); } else if ((returnValue = ct.RWTREC(instanceID, dataFileNumber, dataField)) != 0) { System.out.println(); System.out.println(); System.out.println("Update failed (code " + returnValue + ")."); ct.TRANABT(instanceID); } else { System.out.println(); System.out.println("The following record was updated: "); DisplayARecord(dataField); ct.TRANEND(instanceID); } } public static boolean SelectARecord(ctree ct, String instanceID, String[] dataField, int dataFileNumber) { int returnValue = 0; int keyno = dataFileNumber + CUSTIDX; byte inpbuf[] = new byte[INPBUFSIZ]; Object target[] = new Object[1];

System.out.println(); System.out.print("Enter Customer Number to select: "); System.out.flush(); try { System.in.read(inpbuf); } catch (IOException ioe) { System.err.println(ioe.toString()); ioe.printStackTrace(); } String buffer = new String(inpbuf,0); target[0] = buffer; if ((returnValue=ct.GTEREC(instanceID, keyno, target, dataField)) != NO_ERROR) { isam_error(returnValue); return false; } DisplayARecord(dataField); return true; } public static void DisplayARecord(String[] dataField) { System.out.println(); System.out.println("First Name : " + dataField[2]); System.out.println("Last Name : " + dataField[3]); System.out.println("Address (Line One): " + dataField[4]); System.out.println("Address (Line Two): " + dataField[5]); System.out.println("City : " + dataField[6]); System.out.println("State : " + dataField[7]); System.out.println("Zip Code : " + dataField[8]); System.out.println("Phone Number : " + dataField[9]); System.out.println("Fax Number : " + dataField[10]); System.out.println("email address : " + dataField[11]); System.out.println("Web site URL : " + dataField[12]); System.out.print("Customer Number : " + dataField[1]); } public static void isam_error(int isam_err) { System.out.println(); switch (isam_err) {

case NO_ERROR: break; case DLOK_ERR: System.out.print("Couldn't get lock on record"); break; case INOT_ERR: System.out.print("Key not found."); break; case FNOP_ERR: System.out.print("Could not open file"); break; case KCRAT_ERR: case DCRAT_ERR: System.out.print("Could not create file"); break; case KOPN_ERR: case DOPN_ERR: System.out.print("Tried to create existing file"); break; case KDUP_ERR: System.out.print("Key already in index"); break; default: System.out.print("Error " + isam_err); break; } } public static void finalize(ctree ct,String instanceID, int dataFileNumber) { int returnValue = 0;

System.out.println(); System.out.println("That's all. Shutting down application."); System.out.println();

if ((returnValue = ct.CLRFIL(instanceID,dataFileNumber)) != NO_ERROR) finalize(ct,instanceID, "Could not close ISAM system.", returnValue); ct.STPUSR(instanceID); System.exit(0); } public static void finalize(ctree ct,String instanceID, String termsg,int isam_err) { System.out.println(); System.out.println(termsg);

isam_error(isam_err);

ct.STPUSR(instanceID); System.exit(1); } }

Back to Article

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.