Hyrax - Extending BES Module

From OPeNDAP Documentation
⧼opendap2-jumptonavigation⧽

Writing modules and handlers

Putting it all together into a Module

Once you have created your extensions to the BES you need to be a able to add them to the BES. This is done in the Module class. For example, in the netcdf_handler directory you will see a NCModule class. Within this Module class there is a static function that gets called when the Module is loaded.

extern "C"
{
    BESAbstractModule *maker()
    {
        return new NCModule ;
    }
}

This function must exist in your shared library in order for your module to be loaded into the BES. In the above example, the netcdf_handler module NCModule is created in the maker function.

The Module classes have two methods:

void
NCModule::initialize( const string &modname )
{
    BESDEBUG( "Initializing NC Handlers:" << endl )

    // Your code here

    BESDEBUG( "Done Initializing NC Handlers:" << endl )
}

and

void
NCModule::terminate( const string &modname )
{
    BESDEBUG( "Removing NC Handlers:" << endl )

    // Your code here

    BESDEBUG( "Done Removing NC Handlers:" << endl )
}

As you could probably already tell, the initialize method is where you would add all of your extensions and the terminate method is where you would remove them and do any cleanup of your module. This is not to be confused with the initialization and termination callbacks described later. The callbacks are called whenever a request is made to the BES where as the methods in the Module class are called when the module is loaded and when it is unloaded from the BES respectively.

Once you have your new Module class you will need to build the new module. In order to be dynamically loaded by the BES the module needs to be built as a shared object module. The following is a snippet from a Makefile.am from an existing module for netcdf.

lib_besdir=$(libdir)/bes
lib_bes_LTLIBRARIES = libnc_module.la

libnc_module_la_SOURCES = $(MODULE_SOURCES)
libnc_module_la_CPPFLAGS = $(BES_CPPFLAGS)
libnc_module_la_LDFLAGS = -avoid-version -module 
libnc_module_la_LIBADD = $(BES_DAP_LIBS)

The important line to take note of is for the LDFLAGS, where we spcify -avoid-version -module. This will build a .so file for your library that can be loaded into the BES.

Now that you have the shared object module built you need to have it loaded into the BES. To do this you need to locate the line:

BES.modules=dap,dapcmd

and add your new module. There might be more after dapcmd if you have additional modules already loaded in your server. For example, using the netcdf_handler as an example again, we would have the following:

BES.modules=dap,dapcmd,nc

And then you need to tell it where to find that nc module. So you'll need to add the following line:

BES.module.nc=/full/path/to/installation/lib/bes/libnc_module.so

And the next time the BES is started it will know to load the three modules (in this example) dap, dapcmd, and nc. It will know to find the nc module using the full path to the shared object module libnc_module.so.

Throughout this document we will tell you how to add the particular extensions to the Module class' initialization method and how to remove your extensions in the Module class' termination method.

New response types.

A response to a BES request is considered here to be a response type. Whether this response type is a simple informational response, such as a response to 'show help;' or the response type is a data response such as a DAS response in the 'get das for definition;' command. New response types can be added to the BES.

New Response Types

New request/data handlers

A request handler is a handler that knows how to fill in a particular response, such as DAS, DDS, DataDDS, help, and version responses, for example.

New Request Handlers

Containers and how to store them

New ways of storing containers, called container storage. Currently we have built in volatile storage, which means storing new containers only during the duration of your connection with the BES. We also have a way to load containers from a file that can be configured in the initialization file. You could also create user specific container storage, storing container information in a MySQL database, for example.

Containers, and how to store them

Definitions and how to store them

New ways of storing definitions, called definition storage. Currently we have built in volatile storage, which means storing new definitions only during the duration of your connection with the BES. You could also create user specific definition storage, saving off those oft used definitions for later use.

Definitions, and how to store them

Reporting

A report mechanism. Once a request has been completed, the information is passed to any reporters that are registered with the server. This way you can save user information, usage information, data access information, and more.

Keeping Track of Requests

Transmitting responses

Different ways of transmitting the data. The default is to return the data through standard output, which gets redirected back to the client. But, with the ¿get¿ command you can specify a ¿return as¿ command ( see above ) that could save the response to a file, transmit it via email, save it in a new data file like netcdf.

New Transmitters

Initialization and Terminiation Callbacks

Add in your own initialization and termination routines. If you have routines that need to be run when the BES starts up, you can add callback functions for initialization and termination.

Creating Initialization and Termination callbacks

Handling Exceptions

Add in your own exception handling methods. When an exception is thrown within the BES, the exception is handled by the BES Exception Manager. It is possible to register a routine to be called before the default handling is done. This way, if you have a specific way that you want to handle specific exceptions, you can do that here.

Handling Exceptions within the BES

Aggregating your data

By default there are no aggregation routines in the BES. But, the BES provides a mechanism for data to be aggregated.

Providing Aggregation Routines

New commands

You've seen all the default commands available in the BES. Now what if you want to create a new command, or even modify an already existing command.

Creating your own commands

Adding Debugging to your code

The BES has a simple to use debugging tool. When the BES is started up you can specify -d "<output>,<context>" on the command line of the besctl script to enable debugging. The output option is either cerr, which will dump all debug statements to standard error, or a file name, which will dump all debug information to the specified file. The context option is a comma separated list of components. For example:

besctl start -d "bes.debug,all"

This will dump all debug statements to the file bes.debug in the same directory from where you started the BES.

And adding debug statements to your code is easy.

 BESDEBUG( "<context>", "I am here" << endl )

The first parameter to this macro is the context of debugging. For example, if you are debugging the netcdf_handler you would use "nc" as the context. In the BES there is the bes context, ppt for the transport context, server for the server specific context, cmdln for the bescmdln specific debugging. After the context you can add anything that would be accepted by ostream. And, all BES classes are inherited from a single class, BESObj. This class has a method on it that allows you to dump the contents of that object to an ostream. All classes generated by the besCreateModule script also implement this dump method. For example, using the example hello world module provided in the BES source tree:

void
SayResponseHandler::dump( ostream &strm ) const
{
    strm << BESIndent::LMarg << "SayResponseHandler::dump - ("
			     << (void *)this << ")" << endl ;
    BESIndent::Indent() ;
    BESResponseHandler::dump( strm ) ;
    BESIndent::UnIndent() ;
}

In order to dump an object all you have to do is:

SayResponseHandler *handler = new SayResponseHandler( "say" ) ;
BESDEBUG( "handler = " << *handler << endl )

In your Module class you should register your context, so that when -h is specified to beslistener you can see all context available for debugging. To register, in your Module class in the initialize method simply add the following line:

BESDebug::Register( "<context>" ) ;

where context is your component name (nc for netcdf_handler, ff for freeform_handler, etc...)

Submitting your modules

So you've come up with a killer module that does some pretty cool stuff. Well ... share it with the rest of us.

Sharing your BES Modules