10 Writing a Custom Facilities Module

This chapter explains how to write an Oracle Communications Billing and Revenue Management (BRM) Facilities Module (FM) to add new features and functionality.

Creating FMs requires the following knowledge and skills:

For information on creating or modifying policy FMs, see "Adding and Modifying Policy Facilities Modules".

About Customizing System FMs

FMs are sets of opcodes grouped into functional categories in the Connection Manager (CM). FMs implement business semantics, such as billing an account for the purchase of a product, checking credit card information, verifying passwords, and so on. FMs use the business policy decisions implemented in the policy FMs to implement the business processes. CMs pass the FM opcodes translated into base opcodes to the Data Manager (DM).

System FMs access the BRM database and manage specific types of information. You can write custom FMs that access the BRM databases or any custom data storage systems. Your custom FMs can use the existing base opcodes or implement new ones.

For a description of System FMs and policy FMs and instructions on modifying Policy FMs, see "Adding and Modifying Policy Facilities Modules".

By default, BRM includes the policies and features necessary to manage an Internet service through the System FMs.

Important:

The System FMs are part of the base BRM system and you cannot modify them.

The System FMs perform the following functions:

  • Handle registration

  • Manage customer information

  • Track activity

  • Bill customers

The CMs execute the default functionality defined in the System FMs. To add new functionality to BRM, have the CM execute custom functionality by creating custom FMs. For example, you can create custom FMs to store electronic mail and to manage Web pages, ftp archives, and any other form of information.

You need to write a new FM in the following situations:

  • If you implement a custom business policy by defining new opcodes, you need a new FM to process the policies.

  • If you want to create customized operations on storable objects, you need a custom FM to execute the operations.

About Implementing Custom FMs

You implement a custom FM the same way you add a custom client application in C to BRM:

  • You use the same standard client libraries you use to write a custom application. These libraries are included in the BRM SDK (BRM_SDK_Home\lib).

  • You use the same coding conventions in an FM and in custom applications.

  • An FM can call system opcodes and use PCM Library macros just like a client application.

  • You can easily implement new features at the client application level and then migrate them into an FM after debugging.

For information on creating client applications, see "Adding New Client Applications".

FMs are configured into CMs at CM execution time as dynamically loaded libraries. When a new operation is implemented with an FM and the FM becomes part of a CM, it appears to all client applications as a fully integrated BRM operation. The custom FMs can perform checks, write log records, call functions in other FMs, call the default DMs, or call custom DMs.

Creating a New FM

Follow these procedures to create a new FM in a CM:

  1. Define new opcodes. See "Defining New Opcodes".

  2. Define input and output specifications for the new opcodes. See "Defining Input and Output Flist Specifications".

  3. If necessary, define new storable classes and fields. See "Defining New Storable Class and Field Definitions".

  4. Write a function to implement the new opcode. See "Writing a Function to Implement a New Opcode"

  5. Use the fm_post_init function to call non-base opcodes at CM initialization. See "Using the fm_post_init Function to Call Non-Base Opcodes at CM Initialization".

  6. Write a program to map opcodes to functions. See "Creating an Opcode-to-Function Mapping File".

  7. Create a shared library for the new FM. See "Creating a Shared Library for a New FM".

  8. Configure the new FM as part of the CM. See "About Configuring a New FM into a CM".

Defining New Opcodes

You must define your custom opcodes, with their numbers, in a header file and run the parse_custom_ops_fields script on the file. This script creates an extension to the pcm_tbls.c file. Client applications use pcm_tbls.c to map a field or opcode to its number.

For more information on the parse_custom_ops_fields script, see parse_custom_ops_fields.

BRM opcodes and their numbers are defined in the ops/*.h files in the BRM_Home/include directory, where BRM_Home is the directory in which you installed BRM components.

To pass new opcodes from a client application to a new FM, you use the PCM_OP macro.

To define a new opcode:

  1. Create a header file and define your new opcodes by using this format:

    #define opcode_name_1          opcode_number_1
      
    

    For example, you might create a header file named my_opcodes.h with these definitions:

    #define MY_OP_SET_AGE       100001
    #define MY_OP_SET_LANGUAGE  100002
    

    Important:

    Numbers up to 10000 are reserved for use by BRM Software.
  2. Run the parse_custom_ops_fields Perl script by using this syntax:

    parse_custom_ops_fields -L language -I input -O output     -P java_package
      
    

    For information on the parse_custom_ops_fields parameters and their valid values, see parse_custom_ops_fields.

  3. For applications written with PCM C, in the pin.conf file for each application, create an entry using this format:

    - - ops_fields_extension_file ops_flds_ext
      
    

    Where ops_flds_ext is the file name and location of the memory-mapped extension file that the parse_custom_ops_fields script created.

  4. For applications written using Java PCM, add the location of the compiled Java classes that the script generated to the CLASSPATH.

  5. Make sure you include your header file both in the application that is calling the opcode and in the custom FM.

Defining Input and Output Flist Specifications

Flists are passed uninterpreted between the client application and the new FM by the PCM_OP opcode.

Follow these rules when you define flist specifications for your custom opcode:

  • Your flists must conform to the BRM flist specifications.

    See "About Flists" and "Opcode Input and Output Specifications" for details.

  • Both the input and output flist specifications must contain the PIN_FLD_POID field to identify the object being manipulated.

    For an example, see the input or output flist specifications for any of the opcode descriptions.

  • The client, the custom FM, and the custom Data Manager must agree on the flist format and content, especially if you are defining new fields.

Defining New Storable Class and Field Definitions

You may have to create custom fields and storable classes for your FM. As with new opcodes and flists, the custom FM and the client application must agree on the semantics of the new fields.

If you define new classes, you must create a new opcode to manipulate the data stored in the new objects. Pass the new opcode to your custom FM with an flist containing the POID of the storable object. The FM then sends a series of basic opcodes to the DM, depending on the operation.

See "Creating, Editing, and Deleting Fields and Storable Classes" for information on defining new storable classes and fields.

You also can implement custom functionality by using /data storable objects, which contain generic fields and can be used for BLOB (Binary Large Object) processing.

Writing a Function to Implement a New Opcode

To implement a new opcode, you write a new function that calls the base system opcodes to access the DM. The new function then becomes part of a new FM shared library.

The new function you write must conform to the PCM_OP calling convention.

Use the PCM_OP_* reference pages as checklists and templates to determine what your custom function must implement. Pay particular attention to the input and output flists.

Important:

If you are adding a new function in the fm_utils module that can be called outside the module add a prototype of that function in the fm_utils.h.

Using the fm_post_init Function to Call Non-Base Opcodes at CM Initialization

Using the fm_post_init function, custom FMs can call non-base opcodes at CM initialization. To implement this, ensure that the fm_post_init function is a part of the FM shared library.

For example:

void
fm_post_init(int *err)
{
* errp = 0;
.
.
.
.
} 
  

The CM initialization takes place in two phases:

  • In the first phase, the fm_post_init functions are called for all the FM modules specified in the CMs pin.conf file. At this time, it is not possible to call non-base opcodes from the fm_post_init functions.

  • In the second phase, all the fm_post_init methods implemented by all the FM modules are called. At this time, it is possible to call non-base opcodes from the fm_post_init functions.

Creating an Opcode-to-Function Mapping File

You create a configuration program, fm_*_config.c, to map an opcode to the function that implements it. This configuration file is read when a dynamic library is created, and the mapping information is stored in it. When a parent CM is initialized, it configures the opcode-function pairs into itself, and each child CM inherits the same configuration as its parent.

The configuration program contains an array of struct cm_fm_config for each opcode and its corresponding function and the config function at the end.

For a definition of this struct, see the cm_fm.h file in the BRM_SDK_Home/Include directory.

The following example shows an opcode-to-function configuration file.

Important:

Include your .h file with your new custom opcodes.
 /** fm_cust_config.c 1.8 99/02/11      
 *      
  
   #ifndef lint      
   static  char    Sccs_id[] = "@(#)fm_cust_config.c    1.8 11 Feb 1999 ";      
   #endif
  
   #include                       /* for FILE * in pcm.h */      
   #include "pcm.h"      
   #include "ops/cust.h"
   #include "pcp.h"      
   #include "cm_fm.h"
  
/*******************************************************************
 PIN_EXPORT void * fm_cust_config_func();
  
struct cm_fm_config fm_cust_config[] = {
              /* opcode as a int32, function name (as a string) */
              { PCM_OP_CUST_BILLINFO,         "op_cust_billinfo" },
              { PCM_OP_CUST_CREATE_ACCT,      "op_cust_create_acct" },
              { PCM_OP_CUST_CREATE_SERVICE,   "op_cust_create_service" },
              { PCM_OP_CUST_DELETE_ACCT,      "op_cust_delete_acct" },
              { PCM_OP_CUST_INIT_SERVICE,     "op_cust_init_service" },
              { PCM_OP_CUST_NAMEINFO,         "op_cust_nameinfo" },
              { PCM_OP_CUST_STATUS,           "op_cust_status" },
              { PCM_OP_CUST_VERIFY,           "op_cust_verify" },
              { PCM_OP_CUST_FINDSERV_VERIFY,  "op_cust_findserv" },
              { 0,    (char *)0 }
void *
fm_cust_config_func()
{
return ((void *) (fm_cust_config));
}
  

For example, when the CM gets a PCM_OP_CUST_CREATE_ACCT() opcode through the PCM_OP() call from a client application, the CM looks up this table and calls the op_cust_create_acct() function.

Note:

The CM gets the name of this configuration file from its own configuration file.

Creating a Shared Library for a New FM

Each custom FM must be created as a shared library. BRM code is multi-thread (MT) safe. If you require your custom FM to be MT safe, you must make your custom FM code MT safe.

Solaris: See the Solaris documentation for more information on shared libraries (LD(1)) and making your code MT safe (threads(3T)).

Linux: See the Linux documentation for more information on shared libraries (LD(1)) and making your code MT safe (threads(3T)).

AIX: See the AIX documentation for more information on shared libraries (LD(1)) and making your code MT safe (threads(3T)).

About Configuring a New FM into a CM

New FMs are implemented as shared libraries and are dynamically linked to CMs at runtime. You add new FMs to the CM configuration file. When a new CM is started or restarted, it reads its configuration file and loads the listed FMs dynamically.

Child CM processes inherit the configuration information read by their parent CM process. Child CM processes do not read the configuration file when they are forced. You can limit the number of CMs that implement the new opcode by leaving the new opcode out of the parent CM configuration file.

You must also make sure that you use the same database number in the configuration files for your client applications, custom FM, and the DMs.

Adding a New FM Module to the CM Configuration File

The configuration file contains the names of the shared libraries that implement the base and custom opcodes. It also contains the names of the corresponding configuration files that contain the opcode-to-function mappings.

The custom shared library (.so on Solaris, Linux, and HP-UX IA64; and .a on AIX) contains the functions that implement the new opcodes and the opcode-to-function mapping table struct. No opcode-to-function mapping files need to be present when the CMs with the custom FMs are started because this information is already stored in the shared library. However, the name of the shared library and the mapping struct still have to be in the configuration file so that the CM can find and configure them.

For the format and description of the entries for an FM, see "Syntax for Facilities Module (FM) Entries" in BRM System Administrator's Guide.

Use the entries for the system FMs in the default CM configuration file in BRM_SDK_Home/sys/cm as an example to add your custom FM entries:

The following example shows the configuration file entries for two custom FM config files on Solaris:

#
# example CM pin.conf entries for two custom FMs
#
# define new the FM module(s) for this CM
#
- cm    fm_module       ./fm_myfm.so fm_myfm_config - pin
- cm    fm_module       ./fm_anotherfm.so fm_anotherfm_config - pin
  

In this example, ./fm_myfm.so and ./fm_anotherfm.so are the dynamic libraries implementing the new opcodes that are listed in fm_myfm_config and fm_anotherfm_config, respectively. pin is the name chosen for the tag.

Initializing Objects for Multiple Processes

In the CM configuration file, you can specify an initialization function that is called when the CM loads an FM or Policy FM. This function initializes objects that are called in multiple processes or threads.

  1. Create a new file named *_init.c (for example, fm_term_pol_init.c) in the appropriate FM or Policy FM directory (for example, fm_term_pol directory).

  2. Add the new file to the Makefile.user file.

  3. Implement an initialization function in the *_init.c file.

    Example pseudo code:

    pin_flist_t *global_flistp = NULL;
    extern void
    fm_term_pol_init(int32 *errp)
    {
          global_flistp = read from custom objects;
           *errp = PIN_ERR_NONE;
    }
    

    Note:

    In the example above, global_flistp is allocated in the master CM process or thread. When a child process is created, global_flistp is duplicated and still available on the child process.
  4. Add the initialization function to the CM configuration file file, using the following example:

    - cm fm_term_pol ../../lib/fm_term_pol.lib  fm_term_pol_config_func  fm_term_pol_init pin
    

Handling Transactions in Custom FMs

Custom FM code is responsible for starting, aborting, and committing transactions as required depending on the semantics of the opcode. Each base opcode must be surrounded by transactions, unless a transaction is already open when the opcode is called.

All other system opcodes, except for PCM_OP_PYMT_CHARGE and the password opcodes, start transactions if none are open when they are called.

Custom FM transactions must conform to the transaction specifications of the PCM context management functions. See "Context Management Opcodes" for the rules to follow.

For information on system transaction handling, see the Transaction Handling sections of each opcode description.

In your custom FM, you can check for open transactions. See the fm_generic_opcode.c file in BRM_SDK_Home/templates/fm_template for sample code to use.

A custom FM can call other FMs by using the Portal Communications Module (PCM) API. Also, custom FMs use the same PCM API used to call other FMs and Data Managers.

Note:

Calls to the Data Manager are made with base opcodes.

Managing Memory in Custom FMs

To manage memory in your custom FMs, follow these rules:

  • Always use pin_malloc(), pin_free(), pin_realloc(), and pin_strdup() for the get(), set(), take(), and put() operations on flists.

  • Use the standard routines-malloc(3C), free(3C), realloc(3C), and strdup(3C)-for other memory operations.

Opening a New Context in an FM

To open a new context in an FM, use (pin_flist_t)NULL for in_flistp in the PCM_CONTEXT_OPEN call.

See the sample_app.c and sample_search.c code examples for more information.

Compiling and Linking a Custom FM

Use the libraries in BRM_SDK_Home/lib for linking.

See the sample Makefile for BRM_SDK_Home/source/templates/fm_temp/fm_generic_opcode.c. The main routine for a custom opcode should look similar to the op_generic function in the fm_generic_opcode.c file. The calling parameters and their types are required.

The following example shows the list of include files required. Make sure you include your own header file containing your new opcodes (custom_opcodes.h in the following example). This example also shows skeleton code for the new opcode, the cm_fm_config struct, and the new function:

#define PCM_OP_FM_SAMPLE_LOOPBACK               999999
  
struct cm_fm_config fm_bill_config[] = {
         { PCM_OP_FM_SAMPLE_LOOPBACK,           "op_fm_sample_loopback" },
         { 0,    (char *)0 } 
};
  
#include "pcm.h" 
#include "cm_fm.h" 
#include "pin_errs.h" 
#include "pinlog.h" 
#include "custom_opcodes.h"
  
op_fm_sample_loopback(connp, opcode, flags, i_flistp, r_flistpp, ebufp)
         cm_nap_connection_t     *connp;
         int32                   opcode;
         int32                   flags;
         pin_flist_t             *i_flistp;
         pin_flist_t             **r_flistpp;
         pin_errbuf_t            *ebufp; 
{
         PIN_ERR_CLEAR_ERR(ebufp);
  
         /***********************************************************
          * Check for errors.
          ***********************************************************/ 
         if (opcode != PCM_OP_FM_SAMPLE_LOOPBACK) {
                 pin_set_err(ebufp, PIN_ERRLOC_FM,
                         PIN_ERRCLASS_SYSTEM_DETERMINATE,
                         PIN_ERR_BAD_OPCODE, 0, 0, opcode);
                 PIN_ERR_LOG_EBUF (PIN_ERR_LEVEL_ERROR,
                         "op_fm_sample_loopback", ebufp);
                 return;
          }
  
          /***********************************************************
           * Return a copy of our input flist.              
           ***********************************************************/
          *out_flistpp = PIN_FLIST_COPY(in_flistp, ebufp);
  
          return; 
}

Debugging FMs

You can debug custom and policy FMs using BRM SDK and standard programming tools.

To debug FMs and policy opcodes, you need BRM SDK, access to a functioning BRM server, and the programming tools supported by BRM SDK on your platform. See "About BRM SDK" for installation instructions and other information about BRM SDK.

For an overview of the connections required to test an FM, see "Testing New or Customized Policy FMs".

The primary way of debugging an FM is attaching to a running CM.

Various debugging tools are available on the supported operating systems:

  • On Solaris, you can use the dbx utility or Sun WorkShop.

  • On Linux, you can use the gdb debugger.

  • On HP-UX IA64, you can use the WDB debugger.

  • On AIX, you can use dbx.

The following instructions describe debugging in Solaris using dbx. In HP-UX IA64, AIX, and Linux, the debugging tools perform similar tasks, but the specific steps will be different.

  1. Start a CM.

    Make sure the CM pin.conf file has a reference to the compiled FM that you are debugging. Also make sure the pin.conf points to a CM and EM that are valid and running.

  2. Use the ps command to find the process ID of the CM.

  3. Attach dbx to the process.

    dbx cm process_id
      
    
  4. Set follow_fork_mode to both or child depending on whether you want to debug the main CM process or a child process. For example, use this command to set follow_fork_mode to child:

    dbxenv follow_fork_mode child
      
    
  5. Scope to the FM by using either the func or file command.

  6. Set breakpoints at appropriate places in the code.