Friday, September 12, 2008

DB2 thin clients connecting to Linux Web service engines: Part 2

Original Source click here

Level: Intermediate

Bob Swart (drbob@chello.nl), Author, Trainer, Consultant, and Webmaster, Bob Swart Training and Consultancy

15 Jul 2004

This article demonstrates how to build the user interface (a thin-client application) for a SOAP Web service engine with Kylix 3 on Linux, exposing the data from DB2 UDB database tables to the outside world.
Show developerWorks content related to my search: DelphiShow developerWorks content related to my search: Delphi


Introduction

Linux is especially renowned for its (Web) server capabilities. In my last article, DB2 Web Service Engines on Linux with Kylix 3, I started to focus on distributed applications. In the first part, you started by building the server-side "engine" on Linux, exposing the data from the IBM® DB2® Universal Database™ (UDB) SAMPLE database as a SOAP Web service.

This time, you'll build the user interface (the presentation layer), which can be on either Linux (with Borland® Kylix™ for Delphi or C++) or Windows (with Borland Delphi™ or Borland C++Builder™). Whichever platform or language you use, the client application will turn out to be a so-called thin-client application. Easy to deploy and configure, and all that is required is an Internet connection to talk to the Web service that exposes the DB2 database contents.



Back to top


Choosing the client

Borland offers an application framework called DataSnap™ to build distributed applications on Windows and Linux. Last time, you built the DataSnap Server as a SOAP Web service using Kylix 3 on Linux. For a client application to connect to this Web service, you need to use the TSoapConnection component, which is available in Delphi and C++Builder on Windows, as well as Kylix itself.

Where Linux is especially renowned for its server capabilities, the majority of client applications are still on Windows desktops. As a result, as well as an exercise in cross-platform multi-tier architecture, you'll build the client on Windows, using Delphi or C++Builder (the screenshots will be Delphi, but you can perform the same steps using C++Builder, and I will list both Delphi and C++ code in the source code listings).



Back to top


Locating the SOAP Web service

Last time, you implemented two different versions of the Kylix 3 DataSnap Server: a CGI standalone executable which (like an Apache DSO) can be deployed in the cgi-bin directory, as well as a Web App Debugger executable. The latter is especially useful in the development and debugging phase of your project, but should not be used as a deployment solution.

The URL for the CGI version is http://servername/cgi-bin/DataSnapSoapServerDB2. For the Web App Debugger (WAD) version, you need to start the Web App Debugger on the Web server machine, and the URL for the WAD version is http://servername:8081/WAD.WAD.

You get the same service information page using either URL, and you can drill down to the individual interfaces, such as the IK3DB2UDBSAMPLE interface, as shown in Figure 1.


Figure 1. WAD - service info page for K3DB2UDBSAMPLE
Figure 1. WAD - service Info Page for K3DB2UDBSAMPLE

The screenshot of Figure 1 is taken on Windows XP, connecting to the Linux machine (by using the hardcoded IP-address 192.168.92.248, in this case).

As shown in Figure 1, the IK3DB2UDBSAMPLE interface consists of seven methods: AS_ApplyUpdates, AS_GetRecords, AS_DataRequest, AS_GetProviderNames, AS_GetParams, AS_RowRequest, and AS_Execute. The prefix AS_ here stands for AppServer, and defines the DataSnap provider interface which is defined by Borland. The interface is implemented by the SOAP Data Module in the Kylix 3 DataSnap Soap Server, and will be called and used by the SOAP clients.



Back to top


Importing the SOAP Web service

Now that you know the URLs to work with, start Delphi 7 Enterprise (or C++Builder 6 Enterprise) to build a client for this SOAP Web service. Create a new project, and save it in DB2SoapClient.dpr. As a first step, you should build the connection to the DB2 UDB SAMPLE SOAP Server. For this, you need to drop a TSoapConnection component from the WebServices tab of the Component Palette. This component has a URL property that needs to point to the SOAP endpoint of the IK3DB2UDBSAMPLE interface on the Linux machine. I always recommend to start connecting to the Web App Debugger version of the SOAP Web service, so you can debug (on both machines) easier. Once everything works, you can switch to the CGI (or Apache) version of the SOAP Web service with only a minor change, as I'll show at the end of this article.

In order to connect to the Web App Debugger version of the SOAP Web service, the URL property of the TSoapConnection component should get the value http://192.168.92.248:8081/WAD.WAD/soap/IK3DB2UDBSAMPLE. You must also make sure that the UseSOAPAdapter property is set to false, and then you can set the Connected property to true. Note that this doesn't really connect to the SOAP Web service - it only prepares some internal fields of this component. The actual connection - and verification, if things have been set up correctly - will follow in the next step.

In order to use the TSoapConnection component and get data from the DB2 UDB SAMPLE database, you now need to place a TClientDataSet component next to the TSoapConnection component. The RemoteServer property of the TClientDataSet specifies the connection component that communicates with the remote DataSnap server, and should be pointing to the TSoapConnection component. If everything is working right, you can now open up the drop-down combobox for the ProviderName property, and select a name from the list of exported TDataSetProvider components that were placed on the SOAP Data Module (in the Kylix 3 SOAP Web service). You only placed one, called dspEMPLOYEE, but you should get this name if everything was set up correctly and the connection to the Kylix 3 Web service on Linux can be made.



Back to top


Tracing potential problems...

If the drop-down list for the ProviderName remains empty, then there is a connection or database problem along the way. In order to find out more details about the problem, you can manually enter the name of the Provider in the ProviderName property (that's dspEMPLOYEE), and then manually set the Active property of the TClientDataSet component to true. This will raise an exception, resulting in an errormessage in a dialog, but at least it will tell you more details about the problem. Possible errors that I've encountered include "DataSet not assigned" (when I forgot to assign the DataSet property of the TDataSetProvider in the DataSnap server), or a more database-specific error that I had to resolve at the Web server.



Back to top


Showing data at design-time

Once the ProviderName has received a value, you can request data to be sent from the SOAP Web service to the TClientDataSet component by setting the Active property to true. This should only be done at design-time to test the connection. You should not leave this property set to true when you close the project, since that means that a request for data will automatically be sent to the SOAP Web service as soon as you (or somebody else) re-opens the project. And this can be time-consuming, or it can even fail, for example, if the development machine where you open the project has no Internet access, or is unable to connect to the SOAP Web service machine. So the recommendation is to set the Active property at run-time, as you'll do in a minute.

At design-time, you can use the Active property to feed data into the TClientDataSet component, and watch the results (also at design-time). In order to view the data, which is data from the EMPLOYEE table of the DB2 UDB database, you need some data-aware controls. First, place a TDataSource component next to the TClientDataSet, and point its DataSet property to the TClientDataSet component.

You can now add components like the TDBGrid, TDBNavigator, etc. and connect their DataSource property to the TDataSource component. Since the TClientDataSet is already active (at design-time), you can immediately see the live data, as shown in Figure 2.


Figure 2. Delphi 7 DataSnap Client
Figure 2. Delphi 7 DataSnap Client

Note that the data that you see in the TDBGrid is from the EMPLOYEE table which is part of the DB2 UDB SAMPLE database on Linux. The Kylix 3 SOAP Web service has transported records from the EMPLOYEE table to your thin-client.



Back to top


Connecting at run-time

Although it's nice to see the data at design-time, I've also explained why it's better to set the Active property of the TClientDataSet to false (when saving and compiling the project). This means you have to explicitly open the TClientDataSet at run-time, which can be done when the Form is created in the OnCreate event handler. Using Delphi, this means writing the following single line of code:

procedure TForm1.FormCreate(Sender: TObject);
begin
ClientDataSet1.Active := True;
end;

In C++Builder, that would be as follows:

void __fastcall TForm1::FormCreate(TObject *Sender)
{
ClientDataSet1->Active = true;
}

When the TClientDataSet component is activated, it will automatically request data from the specified provider, through the connection component, resulting in a connection to the server, which returns the DB2 UDB SAMPLE database data.



Back to top


Working with data

With the data being displayed in the TDBGrid (or any other visual data-aware control), the end user should be allowed to work with the data - making changes, and sending updates back to the middleware application layer (the Kylix 3 DataSnap Web service) which applies the updates to the underlying DB2 UDB SAMPLE database.

The TClientDataSet is only an in-memory table at the client side, and changes to this dataset will not be saved automatically. You need to explicitly call the ApplyUpdates method in order to send the updates from the client to the server and the DB2 DBMS. This can be done either implicitly (for example, in response to an OnAfterPost and OnAfterDelete event), or explicitly using a button with the "Apply Updates" caption. The latter is implemented in Delphi as follows:

procedure TForm1.btnApplyUpdatesClick(Sender: TObject);
begin
ClientDataSet1.ApplyUpdates(0)
end;

With similar syntax for C++Builder developers:

void __fastcall TForm1::btnApplyUpdatesClick(TObject *Sender)
{
ClientDataSet1->ApplyUpdates(0);
}



Back to top


Handling update errors

Sometimes, sending an update from a client does not result in an update, for example when the record was changed by another user, or if the record that you wish to update was already deleted by another user (the record could not be found anymore).

If you want to notify the end user of this situation when it occurs, then you need to write some code in the OnReconcileError event handler of the TClientDataSet component. If you only want to show the error message, the code can be as follows:

procedure TForm1.ClientDataSet1ReconcileError(
DataSet: TCustomClientDataSet; E: EReconcileError;
UpdateKind: TUpdateKind; var Action: TReconcileAction);
begin
ShowMessage(E.Message)
end;

However, it would be more useful to allow the end user to actually correct the problem, and reapply the updates. For this, you can build your own dialog, using the Reconcile Error dialog that is part of Borland Delphi and C++Builder (and can be found in the Dialogs category of the Object Repository). To get this dialog, do File | New - Other, go to the Dialogs tab of the Object Repository, and select the Reconcile Error dialog. Save it in a file like ErrorDialog.pas (or ErrorDialog.cpp for C++Builder).

Then return to the unit with your client form, press Alt+F11 and add the ErrorDialog unit to the uses clause. Apart from hosting the Reconcile Error dialog, the unit also contains a single function that can be called in order to handle the interaction between the dialog and the end user. So the Delphi code for the OnReconcileError event can be recoded as follows:

procedure TForm1.ClientDataSet1ReconcileError(
DataSet: TCustomClientDataSet; E: EReconcileError;
UpdateKind: TUpdateKind; var Action: TReconcileAction);
begin
Action := HandleReconcileError(DataSet, UpdateKind, E)
end;

C++Builder developers need to pass an additional (first) Owner argument to the HandleReconcileError function, by the way:

void __fastcall TForm1::ClientDataSet1ReconcileError(
TCustomClientDataSet *DataSet, EReconcileError *E,
TUpdateKind UpdateKind, TReconcileAction &Action)
{
Action = HandleReconcileError(Owner, DataSet, UpdateKind, E);
}



Back to top


Undoing updates before applying

Apart from allowing the end user to have control over when the updates are being sent to the middleware server (and the underlying IBM DB2 DBMS), my experience is that most end users sometimes make small mistakes, and want to undo changes that they've made. If a change is already applied to the server, then you just have to change the record again (to the original or correct state), re-apply the update, and hope for the best.

If the changes are not yet applied to the server, then it is even easier. Since the TClientDataSet component contains all the requested records and their changes in memory, it can undo all changes the end user has made (all the way back to the original version of the records that came from the last time you requested data or applied the updates).

There are three ways to undo changes, and you'll implement two of them. You can undo the last change, by calling the UndoLastChange method. If you pass true as argument, you'll be taken to the record that was just changed back (otherwise, if you're in a TDBGrid, you may not notice any effect of calling the UndoLastChange method).

The CancelUpdates method will undo all changes that are currently present in the TClientDataSet component - so you'll be taken back to the situation where you last requested data (or when you last applied updates to the server).

Calling these methods can be done in the OnClick event handlers of two buttons, called btnUndo and btnUndoAll with the "Undo" and "Undo All" captions. Using Delphi, this is coded as follows:

procedure TForm1.btnUndoClick(Sender: TObject);
begin
ClientDataSet1.UndoLastChange(True)
end;

procedure TForm1.btnUndoAllClick(Sender: TObject);
begin
ClientDataSet1.CancelUpdates
end;

And for C++Builder developers, the syntax is as follows:

void __fastcall TForm1::btnUndoClick(TObject *Sender)
{
ClientDataSet1->UndoLastChange(true);
}

void __fastcall TForm1::btnUndoAllClick(TObject *Sender)
{
ClientDataSet1->CancelUpdates();
}

The third method - which I won't include in this article - is by calling the RevertRecord. This will undo all changes on the current selected record. Although it's also powerful, this particular option is less often requested by customers, so I seldom implement it. But at least you know it's available now, so you can add a third button, with the "Undo Current" caption.

Figure 3 shows the current user interface as it should appear in C++Builder 6 at design-time (note that the TClientDataSet is still active at this time, however).


Figure 3. C++Builder 6 DataSnap Client with Apply Updates, Undo and Undo All buttons
Figure 3. C++Builder 6 DataSnap Client with Apply Updates, Undo and Undo All buttons

You can now save the client projects, and compile and run them (make sure to set the Active property of the TClientDataSet to false, so it won't be open at design-time).



Back to top


Changing the server

So far, the client applications have been using the Kylix 3 Web App Debugger version of the SOAP Web service executable on the Linux machine. However, for real-world deployment, you obviously want to switch to the CGI or Apache DSO server version of the SOAP Web service instead. In order to make this change, all you need to do is modify the URL property of the TSoapConnection component. Right now, the value is:

http://192.168.92.248:8081/WAD.WAD/soap/IK3DB2UDBSAMPLE

But if you want to connect to the CGI executable, you need to change that to:

http://192.168.92.248/cgi-bin/DataSnapSoapServerDB2/soap/IK3DB2UDBSAMPLE

Note that DataSnapSoapServerDB2 is the name of the CGI executable. In both cases, the hardcoded IP-address 192.168.92.248 is pointing to a Linux server on my local intranet, and should be replaced with an IP number or server name in your network.



Back to top


Easy deployment

Note that the client application doesn't know which database the data is coming from. All it knows is that it receives data from the dspEMPLOYEE DataSetProvider, through a TSoapConnection component. The fact that a DB2 UDB SAMPLE database is used by the server is unknown to this client (and the client doesn't require the DB2 client drivers installed to work).

The Client application only required the MIDAS.dll to be deployed with it, and Delphi developers can even skip that one if they add the MidasLib unit to the uses clause of the project (this is unfortunately not available for C++Builder developers). So using Delphi, you can end up with a standalone executable that can be placed anywhere on a client machine. No other run-time libraries are required.

Also, as mentioned last time as well, a royalty-free unlimited DataSnap deployment license is included with the purchase of Delphi 7 Studio Architect, Delphi 7 Studio Enterprise, C++Builder 6 Studio Enterprise, and Kylix 3 Enterprise.



Back to top


Summary

In this article, I have briefly introduced the concept of distributed applications. Last time, you saw what it takes to build a Web service "engine" application using Kylix 3, exposing the DB2 UDB SAMPLE database on the Web as a SOAP Web service on Linux. This time, you've built a thin-client application that connects to this DataSnap Soap Server that exposes the DB2 UDB SAMPLE database. The client displays the data, allows the end user to modify the contents (or undo the changes), sends updates back to the data layer through the middleware layer, and optionally responds to update errors.

Using Kylix 3 and DataSnap to build distributed applications based on data from the DB2 UDB databases offer greater security, performance, and easier maintenance. For the thin client applications, it also means easier deployment.




Back to top



0 Comments: