Implementation of GDA Providers

As a sample implementation of a GDA provider the gda-odbc will be used.

Makefile.am Entires for GDA Providers

First the relevant parts of the Makefile.am will be introduced.

(1)	    bin_PROGRAMS  = gda-odbc-srv

(2)	    lib_LTLIBRARIES = libgda_odbc.la 
	    
(3)	    IDLFILES = $(top_srcdir)/gda/gda.idl \
	             $(top_srcdir)/gda/gda-command.idl \
	             $(top_srcdir)/gda/gda-connection.idl \
	             $(top_srcdir)/gda/gda-error.idl \
	             $(top_srcdir)/gda/gda-fieldx.idl \
	             $(top_srcdir)/gda/gda-parameter.idl \
	             $(top_srcdir)/gda/gda-recordset.idl

(4)	    INCLUDES = -I. \
  	             -DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
	             -I$(includedir) \
	             $(ORB_CFLAGS) \
	             $(GNOME_INCLUDEDIR)
(5)
	    gda_odbc_srv_SOURCES = main.c
(6)
	    gda_odbc_srv_LDADD   = \
		     libgda_odbc.la \
		     $(top_builddir)/iodbc/libiodbc.la \
		     $(GNORBA_LIBS) \
		     $(INTLLIBS)

(7)	    libgda_odbc_la_SOURCES = \
		     gda-types.h \
		     gda-command.h    \
	        ..........
(8)	             gnome-shlib.c

(9)	    gda.h: $(IDLFILES)
    	             orbit-idl -I$(top_srcdir)/gda $(top_srcdir)/gda/gda.idl
	    gda-common.c: $(IDLFIaLES)
	             orbit-idl -I$(top_srcdir)/gda $(top_srcdir)/gda/gda.idl
	  

31Makefile.am Entries

(1)
The name of the executable provider. The name should have the prefix gda and the suffix srv.
(2)
The name of the shared library provider. The name should have the prefix libgda and no suffix.
(3)
The IDL files for the GDA Client/Server interface.
(4)
Include files used by the provider
(5)
Minimalistic main function for the provider.
(6)
Libraries needed for the provider. Note that the shared library type provider libgda_odbc.la is also mentioned here. The main implementation is done in this shared library and the main program only provides initialization of ORBit and the shared library.
(7)
Enumeration of the files which implement the provider. The gnome-shlib.c entry is mandatory.
(8)
Initialization functions and data structures for ORBit shared library servers. From functions in this file the initialization functions of the server should be called.
(9)
Rules to generate the include file, the skeleton and the common CORBA files from the IDL files.

main Function for Executable Type Providers

(1)	  int main(int argc, char* argv[])
	  {
	    CORBA_ORB                 orb;
	    CORBA_Environment*        ev;
	    CORBA_Object              connection_factory_obj;
	    CORBA_Object              name_service;
	    gpointer                  servant;
	    PortableServer_POA        root_poa;
	    PortableServer_POAManager pm;
	    CORBA_char*               objref;
	    FILE*                     logstream;
	    struct sigaction          sa;
	    int                       output_fd;

	    sa.sa_handler = SIG_IGN;
	    sigaction(SIGPIPE, &sa, 0);
	    
	    logstream = fopen("/tmp/gda-odbc-srv.log", "a");
	    g_log_set_handler("GDA ODBC", 0xffffffff, g_log_2_file, logstream);

	    ev = g_new0(CORBA_Environment,1);

	    CORBA_exception_init(ev);

	    orb = gnome_CORBA_init("gda-odbc-srv", "0.1", &argc, argv, GNORBA_INIT_SERVER_FUNC, ev);
	    Exception(ev);
	    root_poa = (PortableServer_POA)CORBA_ORB_resolve_initial_references(orb, "RootPOA", ev);
	    Exception(ev);
	    
	    connection_factory_obj = connection_factory__create(root_poa, &servant, ev);
	    
	    Exception(ev);
	    objref                 = CORBA_ORB_object_to_string(orb, connection_factory_obj, ev);
	    Exception(ev);
	    name_service = gnome_name_service_get();
	    if (!CORBA_Object_is_nil(name_service, ev))
	      {
	         goad_server_register(name_service, connection_factory_obj, "gda-odbc", "object", ev);
	         printf("%s\n", objref); fflush(stdout);
	      }
	    output_fd = fileno(logstream);
	    setvbuf(stderr, 0, _IOLBF, 0);
	    setvbuf(stdout, 0, _IOLBF, 0);
	    dup2(output_fd, fileno(stdout));
	    dup2(output_fd, fileno(stderr));
	    close(output_fd);
	    pm = PortableServer_POA__get_the_POAManager(root_poa, ev);
	    Exception(ev);
	    PortableServer_POAManager_activate(pm, ev);
	    Exception(ev);
	    CORBA_ORB_run(orb, ev);
	    return 0;
	  }
	  

Writing the CORBA Glue File

One major aspect of all servers is the connection of the CORBA specific part anf the database specific part. Luckily the orbit-idl command supports you with it's -Eskeleton-impl flag. This flag produces a skeleton implementation file called gda-impl.c from the IDL file. This file is used as the starting point for writing the functions to really access the database.

To make this a little bit less painless, I'm adding the following rule to the Makefile.am file
	    implementation:
	    orbit-idl -$$(datadir)/idl -I$(top_srcdir)/gda -Eskeleton_ipml $(top_srcdir)/gda/gda.idl
	  
Then i can reproduce the skeleton file with the simple command make implementation The resulting file will contain all the structure definitions and object create and destroy functions needed for the IDL file. I'll use the Connection object as an example, but the work for all other objects is basically the same. Some of the things i'm doing are just personal taste.

We have to deal with two different objects here. The CORBA Connection object and the Gda_ODBC_Connection object. The CORBA connection object is the CORBA servant, the Gda_ODBC_Connection object is responsible for holding all relevant variables and state information needed for the specific database. Whwnever i use the term Connection object i mean the CORBA Connection object. The term ODBC_Connection will refer to the GDA_ODBC_Connection type.

I'm not sure if this method is a good one, but it decouples the CORBA data structures and the database specific part. Becuase the database specific types are used all over the server implementation, i thought it would not be a god idea to have CORBA data types all over the place. The problem is that for efficiency some CORBA memory allocation functions should be used in the server nayway. For example if memory is allocated to store the result of a VARCHAR(128) column and later passed back to the client, there are two possibilities:

The first method will be the most efficient, but won't be possbile for all cases. The last method is surely the least efficient, but most convenient.

The next thing you have to do is to find the servant structure definitions. They look like
	    typedef struct {
	    	POA_GDA_Connection servant;
	    	PortableServer_POA    poa;
	    	CORBA_long            attr_flags;
	    	CORBA_long            attr_cmdTimeout;
	    	CORBA_long            attr_connectTimeout;
	    	CORBA_char*           attr_dsn;
	    	GDA_CursorLocation    attr_cursor;
	    	CORBA_char*           attr_defaultDsn;
	    	CORBA_char*           attr_provider;
	    	CORBA_boolean         attr_isOpen;
	    	CORBA_char*           attr_version;
	    	GDA_ErrorSeq          attr_errors;
	    } impl_POA_GDA_Connection;
	  
In this structure all the attributes from the IDL file are represented as elements. Of course we need additional members. In the above mentioned example there is no way to store the handle to the database. What I'm doing, is to ignore the attributes generated by orbit-idl and provide my own type for the variables needed to represent. In the case of the GDA_Connection in the ODBC server, this is the Gda_ODBC_Connection type. This type is defined in the gda-odbc-types.h file and therefore i have to include this file at the top of the generated skeleton file. Then i insert a pointer to this type at the end of the structure. Now the impl_POA_GDA_Connnection
	  structure loks like this
	  	    typedef struct {
	    	POA_GDA_Connection servant;
	    	PortableServer_POA    poa;
	    	CORBA_long            attr_flags;
	    	CORBA_long            attr_cmdTimeout;
	    	CORBA_long            attr_connectTimeout;
	    	CORBA_char*           attr_dsn;
	    	GDA_CursorLocation    attr_cursor;
	    	CORBA_char*           attr_defaultDsn;
	    	CORBA_char*           attr_provider;
	    	CORBA_boolean         attr_isOpen;
	    	CORBA_char*           attr_version;
	    	GDA_ErrorSeq          attr_errors;
	        Gda_ODBC_Connection*  cnc;
	    } impl_POA_GDA_Connection;
	  

The next thing I'm doing is to provide a body for all the functions in the file. This might look like unnecessary work, but since the IDL interface is not fixed yet, and not all functions will be implemented by each server at once, this is the most secure way to do this. The function body consiste of one line is is just
	    g_error("%s not implemented", __PRETTY_FUNCTION__);
	  
So I catch all unimplemented functions and don't get bogus return values in the client.

The next thing to do is to change the *__create functions a little bit. Because we store a pointer to the data structure needed to implement a databsee connection in a seperate object, the cnc pointer needs to be initialized when a Connection object is created. For the Connection object this is simple. Find the __create function for the Connection object. The function is called impl_GDA_Connection__create(). The add a call to gda_odbc_connection_new() and asign it's return valeu to the cnc element of the newservant variable. Now the function should look like:
	    static GDA_Connection 
	    impl_GDA_Connection__create(PortableServer_POA poa, CORBA_Environment * ev)
	    {
	    	GDA_Connection retval;
	  	impl_POA_GDA_Connection *newservant;
	  	PortableServer_ObjectId *objid;

	  	newservant = g_new0(impl_POA_GDA_Connection, 1);
	  	newservant->servant.vepv = &impl_GDA_Connection_vepv;
	  	newservant->poa = poa;
	  	newservant->cnc = gda_odbc_connection_new();
   
	  	POA_GDA_Connection__init((PortableServer_Servant) newservant, ev);
	  	objid = PortableServer_POA_activate_object(poa, newservant, ev);
	  	CORBA_free(objid);
	  	retval = PortableServer_POA_servant_to_reference(poa, newservant, ev);
	  
	  	return retval;
	  }
	  
Other *__create functions, like the impl_GDA_Recordset__create() function will need to have the Gda_ODBC_Recordset structure passed as a parameter, because the recordset might be the result of another function call. In this case just edit the interface of the function and pass the Gda_ODBC_ object to the create function.

To illustrate the creation of a new CORBA object, take a look at the impl_GDA_Command_execute( function. This function returns a new Recordset object. The Recordset servant will hold a ODBC_Recordset pointer so that subsequent fetch calls will know which ODBC Recordset to use. The new Recordset is created with a call to the impl_GDA_Recordset__create() function, which takes care of object activation in the POA and the conversion of the Servant to a CORBA Object (GDA_Recordset). As an additional parameter a pointer to the ODBC Recordset object is passed and stored in the rs eleemt of the Recordset servant structure.