Simple (Netbeans)

This sample is a Netbeans project that uses a standard Java application to communicate with a server, retrieve a table of data and present that table in a Swing user interface using JTable.

Getting Started

To get started with this sample you need to have Netbeans installed.

When you load the project in Netbeans you will possibly need to edit the project properties to point the sample to your copies of the com.remobjects.dataabstract.jar and com.remobjects.sdk.jar libraries. These are typically installed in "c:\Program Files (x86)\RemObjects Software\Data Abstract for Java\bin".

Finally by default the sample interacts with the RemObjects sample server at "http://remobjects.com:8099/bin". If you want to load the data from a local instance of Relativity Server then you need to change the URL in "DataModule.java" to the needed address; for instance "http://localhost:7099/bin". The clientChannelNeedsLogin method of "SwingClientWindow.java" needs to be changed to the following.

public boolean clientChannelNeedsLogin(Exception excptn) {
    String RelativityConnectionStringTemplate = "User Id=\"%s\";Password=\"%s\";Domain=\"%s\";Schema=\"%s\"";
    String lLoginString = String.format(RelativityConnectionStringTemplate,
        "simple", "simple", "DASamples", "Simple");
    return fDataModule.getDataAdapter().login(lLoginString);
}

Running the Sample

The basic function of the sample is to retrieve data from the "Clients" table of the "DASampleService" domain.

When the sample first loads it displays an empty window with a toolbar with 4 buttons and a checkbox. The buttons in order are; "Load" which retrieves the data from the server, "Update" sends any changes to the local data back to the server and applies the changes, "Add Row" adds a new row to the table and "Delete Row" deletes the currently selected row from the table. The "use Async" checkbox is used to determine whether synchronous or asynchronous methods are used to communicate with the server for the "Load" and "Update" actions.

The basic flow of the sample is that you "Load" the data from the server which will then be displayed in a table. You can then double click on any field to edit its value, or press the "Add Row" button to add a new row and then double click on fields to add data into those fields. Then press the "Update" button to send the local changes to the server.

Examining the Code

The sample is comprised of two classes. "DataModule" which creates the basic components required for communicating with the server and the DataTable & DataTableModel that will hold the data retrieved from the server and interact with the Swing JTable. The "SwingClientWindow" handles the user interface interaction.

While this sample demonstrates using both synchronous and asynchronous methods of communicating with the server, typically you would want to use the asynchronous methods to return control back to the application so that the user interface remains responsive to the user.

Connecting to the server

Creating a connection to the server is handled in 2 stages. The first is in the initComponents method of "DataModule", which creates a RemoteDataAdapter which is the gateway for the client to the data in the server be it for retrieving data or applying changes back to the server.

The constructor takes three arguments, the first if the address of the server to connect to which should be passed as a URI object. The second is the name of the data service that the adapter should connect to, and the third is the name of the login service. Typically both of those arguments are simply "DataService" and "LoginService".

If you wish you can increase the timeout period for the http connection, in this sample it is set to 5000.

// DataModule.java - initComponents method
fDataAdapter = new RemoteDataAdapter(URI.create("http://remobjects.com:8099/bin"), "DataService", "LoginService");

((HttpClientChannel)fDataAdapter.getClientChannel()).setTimeout(5000);

The second step is to provide login details for the connection, this sample demonstrates two different ways you can register to provide them.

The first involves providing a ClientChannel callback which implements the clientChannelNeedsLogin method. In that method you would call the login method providing the username and password (or using an instance of Relativity Server you would also pass the "Domain" and "Schema" names). You would also override the requestFailWithException method so you can handle cases where the login attempt fails (wrong username/password, connection dropped and so on).

// SwingClientWindow.java - formWindowOpened method
fDataModule.getDataAdapter().getClientChannel().setChannelCallback(new ClientChannel.IClientChannelCallback() {

            @Override
            public boolean clientChannelNeedsLogin(Exception excptn) {
                return fDataModule.getDataAdapter().login("123", "123");
            }

            // Alternative Login method for connecting to an instance of Relativity
//            @Override
//            public boolean clientChannelNeedsLogin(Exception excptn) {
//                String RelativityConnectionStringTemplate = "User Id=\"%s\";Password=\"%s\";Domain=\"%s\";Schema=\"%s\"";
//                String lLoginString = String.format(RelativityConnectionStringTemplate,
//                        "simple", "simple", "DASamples", "Simple");
//                return fDataModule.getDataAdapter().login(lLoginString);
//            }

            @Override
            public void requestFailWithException(Exception excptn) {

            }
        });

An alternative option is to add a listener to the RemoteDataAdapter which will be called when the login credentials are needed. One option, used here, is to pass an anonymous instance of AbstractRemoteDataAdapterListener to the addListener method of RemoteDataAdapter and implement the loginNeeded method.

// SwingClientWindow.java - formWindowOpened method

//        fDataModule.getDataAdapter().addListener(new AbstractRemoteDataAdapterListener() {
//            
//            @Override
//            public void loginNeeded(LoginNeededEvent lne) {
//                if (fDataModule.getDataAdapter().login("123", "123")) {
//                    lne.setRetry(true);
//                    lne.setLoginSuccessful(true);
//                }
//            }
//        });

Equally your class could implement the RemoteDataAdapterListener interface and implement the loginNeeded method.

Setting up the Table

To present the retrieved DataTable in an instance of Swing's JTable class you need to pass the DataTableModel object to the jtblClients object. The DataTableModel is a concrete implementation of the Swing "AbstractTableModel" and "TableDataChangedListener" classes that provides support for the DataTable class.

From this point any updates to the table by double clicking on a field and changing the data will be passed through to the underlying DataTable object.

// SwingClientWindow.java - formWindowOpened method
jtblClients.setModel(fDataModule.getClientsTableModel());

Retrieving data

To retrieve data from the server pass the DataTable to the fill or fillAsync method of RemoteDataAdapter.

If using the fillAsync method you need to pass a Callback object that will be called when the communication with the server is finished. You can create an anonymous Callback object by using the handy factory method provided by FillRequestTask. Then override the completed method to carry out whatever action you need, for instance to update the status bar to indication if the operation succeeded or present an error dialog.

// SwingClientWindow.java - jButtonLoadDataActionPerformed method
final DataTable clients = fDataModule.getClientsTable();
if (!fUseAsync) {
        fDataModule.getDataAdapter().fill(clients);

        statusLabel.setText("Fill request completed synchronously");
        statusPanel.setBackground(this.getBackground());
} else {

        fDataModule.getDataAdapter().fillAsync(clients, new FillRequestTask.Callback() {

                @Override
                public void completed(FillRequestTask aTask, Object aState) {
                    // snipped code
                }
        }).execute();

        statusLabel.setText("Fill request invoked asynchronously");
        statusPanel.setBackground(Color.yellow.brighter());
}

Adding a row

To add a new row to the table you need to pass an array of Objects to the loadDataRow method of DataTable. Remember that the new row is only in the local data, to add it to the server you need to apply the changes back to the server.

// SwingClientWindow.java
private void jButtonAddRowActionPerformed(java.awt.event.ActionEvent evt) {

        DataTable lTable = fDataModule.getClientsTable();

        if (lTable != null) {
                Object[] lData = {"{" + UUID.randomUUID().toString() + "}", "<ClientName here>", null, null, null, 0.0};
                lTable.loadDataRow(lData);
        }
}                                             

Deleting a row

To delete a row from the underlying DataTable, you need to delete it from the DataTableModel. Simply pass the index of the row you wish to delete to the getRow method, which will return a DataRow whose delete method you then need to call.

// SwingClientWindow.java
private void jButtonDeleteRowActionPerformed(java.awt.event.ActionEvent evt) {
        int idx = this.jtblClients.getSelectedRow();
        if (idx != -1) {
                System.out.printf("Table selected index: %d\n", idx);

                fDataModule.getClientsTableModel().getRow(idx).delete();
        }
}

Applying changes

To apply any local changes to the server you need to pass the changed DataTable to either the applyChanges or applyChangesAsync method of RemoteDataAdapter. Any additional server side changes will also be applied to the local table so that both are in sync.

If using the applyChangesAsync method you need to pass a Callback object that will be called when the communication with the server is finished. You can create an anonymous Callback object by using the handy factory method provided by UpdateRequestTask. Then override the completed method to carry out whatever action you need, for instance to update the status bar to indication if the operation succeeded or present an error dialog.

// SwingClientWindow.java - jButtonUpdateDataActionPerformed method
        DataTable clients = fDataModule.getClientsTable();
        if (clients != null) {
                if (!fUseAsync) {
                        fDataModule.getDataAdapter().applyChanges(clients);
                        statusLabel.setText("Fill request completed synchronously");
                        statusPanel.setBackground(this.getBackground());
                } else {
                        fDataModule.getDataAdapter().applyChangesAsync(clients, new UpdateRequestTask.Callback() {

                                @Override
                                public void completed(UpdateRequestTask aTask, Object aState) {
                                    // code snipped
                        }).execute();

                        statusLabel.setText("Update request invoked asynchronously");
                        statusPanel.setBackground(Color.yellow.brighter());
                }
        }