Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java Native Methods and Legacy Databases


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);


</p>

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;


</p>
    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; 


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


</p>
        String  instanceID = "contacts";
        
        String  userID = null;
        String  userPassword = null;
        String  serverName = "FAIRCOMS@localhost";


</p>
        String  dataField[] = new String[NUMFLD];
        String  ifilInfo[]  = new String[6];
        String  dodaInfo[]  = new String[NUMFLD+1];


</p>
        System.out.println();
        System.out.println("CONTACTS: j-tree sample application");
        System.out.println();


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


</p>
        /* 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;


</p>
                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;


</p>
        System.out.println();
        System.out.println("ADD NEW DATA");
        System.out.println();


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


</p>
        GetInput(dataField);


</p>
        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];


</p>
        System.out.println();


</p>
        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];


</p>
        choice[0] = '\0';


</p>
        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];


</p>
        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];


</p>
        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) {


</p>
            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;


</p>
        System.out.println();
        System.out.println("That's all.  Shutting down application.");
        System.out.println();


</p>
        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);


</p>
        isam_error(isam_err);


</p>
        ct.STPUSR(instanceID);
        System.exit(1);
    }
}

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.