// 
// 
// Copyright (C) 2005-2006 SIPez LLC.
// Licensed to SIPfoundry under a Contributor Agreement.
// 
// Copyright (C) 2004 SIPfoundry Inc.
// Licensed by SIPfoundry under the LGPL license.
// 
// Copyright (C) 2004 Pingtel Corp.
// Licensed to SIPfoundry under a Contributor Agreement.
// 
// $$
//////////////////////////////////////////////////////////////////////////////

// SYSTEM INCLUDES

// APPLICATION INCLUDES
#include "utl/UtlInt.h"
#include "utl/UtlLongLongInt.h"
#include "utl/UtlSListIterator.h"
#include "os/OsLock.h"
#include "os/OsDateTime.h"
#include "os/OsFS.h"
#include "os/OsSysLog.h"
#include "net/Url.h"
#include "fastdb/fastdb.h"

#include "xmlparser/tinyxml.h"
#include "sipdb/RegistrationBinding.h"
#include "sipdb/RegistrationDB.h"
#include "sipdb/RegistrationRow.h"
#include "sipdb/ResultSet.h"
#include "sipdb/SIPDBManager.h"

REGISTER( RegistrationRow );


// Smart DB accessor that attaches the DB on construction, and detaches on destruction.
// This is necessary to protect process/thread-local storage integrity.
class SmartDbAccessor
{
public:
   SmartDbAccessor(dbDatabase* fastDB, int lineNo) : mFastDB(fastDB), mLineNo(lineNo)
      {
         if (mFastDB != NULL)
         {
            mFastDB->attach();
         }
         else
         {
            OsSysLog::add(FAC_DB, PRI_ERR, "RegistrationDB.cpp line %d - no DB", lineNo);
         }
      }
   ~SmartDbAccessor()
      {
         if (mFastDB != NULL)
         {
            mFastDB->detach(0);
         }
      }

private:
   dbDatabase* mFastDB;
   int         mLineNo;
};

#define SMART_DB_ACCESS SmartDbAccessor accessor(m_pFastDB, __LINE__)


// Static Initializers
RegistrationDB* RegistrationDB::spInstance = NULL;
OsMutex         RegistrationDB::sLockMutex (OsMutex::Q_FIFO);

UtlString RegistrationDB::gIdentityKey("identity");
UtlString RegistrationDB::gUriKey("uri");
UtlString RegistrationDB::gCallidKey("callid");
UtlString RegistrationDB::gContactKey("contact");
UtlString RegistrationDB::gQvalueKey("qvalue");
UtlString RegistrationDB::gInstanceIdKey("instance_id");
UtlString RegistrationDB::gGruuKey("gruu");
UtlString RegistrationDB::gCseqKey("cseq");
UtlString RegistrationDB::gExpiresKey("expires");
UtlString RegistrationDB::gPrimaryKey("primary");
UtlString RegistrationDB::gUpdateNumberKey("update_number");

UtlString RegistrationDB::nullString("");

/* ============================ CREATORS ================================== */

/*
 *Function Name: RegistrationDB Constructor
 *
 *Parameters:
 *
 *Description: This is a protected method as this is a singleton
 *
 *Returns:
 *
 */
RegistrationDB::RegistrationDB( const UtlString& name ) :
    mDatabaseName( name )
{
    // Access the shared table databse
    SIPDBManager* pSIPDBManager = SIPDBManager::getInstance();
    m_pFastDB = pSIPDBManager->getDatabase(name);

    // If we are the first process to attach
    // then we need to load the DB
    int users = pSIPDBManager->getNumDatabaseProcesses(name);
    if ( users == 1 )
    {
        // Load the file implicitly
        this->load();
    }
}

RegistrationDB::~RegistrationDB()
{
    OsSysLog::add(FAC_DB, PRI_DEBUG, "<><>## RegistrationDB:: DESTRUCTOR");
}

/* ============================ MANIPULATORS ============================== */

void
RegistrationDB::releaseInstance()
{
    OsSysLog::add(FAC_DB, PRI_DEBUG, "<><>## RegistrationDB:: releaseInstance() spInstance=%p", spInstance);

    // Critical Section here
    OsLock lock( sLockMutex );

    // if it exists, delete the object and NULL out the pointer
    if (spInstance != NULL) {

        // unregister this table/process from the IMDB
        SIPDBManager::getInstance()->removeDatabase ( spInstance->mDatabaseName );

        // NULL out the fastDB pointer also
        spInstance->m_pFastDB = NULL;

        delete spInstance;
        spInstance = NULL;
    }
}

OsStatus
RegistrationDB::load()
{
    OsStatus result = OS_SUCCESS;

    if ( m_pFastDB != NULL )
    {
        UtlString fileName =
            SIPDBManager::getInstance()->
                getConfigDirectory() +
                OsPath::separator + mDatabaseName + ".xml";

        OsSysLog::add(FAC_DB, PRI_DEBUG, "RegistrationDB::load loading \"%s\"",
                    fileName.data());

        TiXmlDocument doc ( fileName );

        // Verify that we can load the file (i.e it must exist)
        if( doc.LoadFile() )
        {
            TiXmlNode * rootNode = doc.FirstChild ("items");
            if (rootNode != NULL)
            {
                // the folder node contains at least the name/displayname/
                // and autodelete elements, it may contain others
                for( TiXmlNode *itemNode = rootNode->FirstChild( "item" );
                     itemNode;
                     itemNode = itemNode->NextSibling( "item" ) )
                {
                    // Create a hash dictionary for element attributes
                    UtlHashMap nvPairs;

                    for( TiXmlNode *elementNode = itemNode->FirstChild();
                         elementNode;
                         elementNode = elementNode->NextSibling() )
                    {
                        // Bypass comments and other element types only interested
                        // in parsing element attributes
                        if ( elementNode->Type() == TiXmlNode::ELEMENT )
                        {
                            UtlString elementName = elementNode->Value();
                            UtlString elementValue;

                            result = SIPDBManager::getAttributeValue (
                                *itemNode, elementName, elementValue);

                            if (result == OS_SUCCESS)
                            {
                                UtlString* collectableKey =
                                    new UtlString( elementName );
                                UtlString* collectableValue =
                                    new UtlString( elementValue );
                                nvPairs.insertKeyAndValue (
                                    collectableKey, collectableValue );
                            } else if ( elementNode->FirstChild() == NULL )
                            {
                                // Null Element value creaete a special
                                // char string we have key and value so insert
                                UtlString* collectableKey =
                                    new UtlString( elementName );
                                UtlString* collectableValue =
                                    new UtlString( SPECIAL_IMDB_NULL_VALUE );
                                nvPairs.insertKeyAndValue (
                                    collectableKey, collectableValue );
                            }
                        }
                    }
                    // Insert the item row into the IMDB
                    insertRow ( nvPairs );
                }
            }
        } else
        {
            OsSysLog::add(FAC_DB, PRI_WARNING, "RegistrationDB::load failed to load \"%s\"",
                    fileName.data());
        }
    } else
    {
        OsSysLog::add(FAC_DB, PRI_ERR, "RegistrationDB::load failed - no DB");
        result = OS_FAILED;
    }
    return result;
}

/// Garbage collect and persist database
///
/// Garbage collect - delete all rows older than the specified
/// time, and then write all remaining entries to the persistent
/// data store (xml file).
OsStatus RegistrationDB::cleanAndPersist( const int &newerThanTime )
{
    OsStatus result = OS_SUCCESS;

    if ( m_pFastDB != NULL )
    {
        SMART_DB_ACCESS;

        // Purge all expired field entries from the DB
        // Note: callid set to null indicates provisioned entries and
        //        these should not be removed
        dbCursor< RegistrationRow > expireCursor( dbCursorForUpdate );
        dbQuery query;
        query = "expires <", static_cast<const int&>(newerThanTime), " and (callid != '#')";
        int rows = expireCursor.select( query );
        if ( rows > 0 )
        {
            OsSysLog::add( FAC_SIP, PRI_DEBUG
                          ,"RegistrationDB::cleanAndPersist cleaning out %d rows\n"
                          ,rows
                          );
            expireCursor.removeAllSelected();
        }

        UtlString fileName =
            SIPDBManager::getInstance()->
                getConfigDirectory() +
                OsPath::separator + mDatabaseName + ".xml";

        // Search our memory for rows
        dbCursor< RegistrationRow > cursor;

        // Select everything in the IMDB and add as item elements if present
        rows = cursor.select();
        if ( rows > 0 )
        {
            OsSysLog::add( FAC_SIP, PRI_DEBUG
                          ,"RegistrationDB::cleanAndPersist writing %d rows\n"
                          ,rows
                          );

            // Create an empty document
            TiXmlDocument document;

            // Create a hard coded standalone declaration section
            document.Parse ("<?xml version=\"1.0\" standalone=\"yes\"?>");

            // Create the root node container
            TiXmlElement itemsElement ( "items" );
            itemsElement.SetAttribute( "type", mDatabaseName.data() );

            int timeNow = OsDateTime::getSecsSinceEpoch();
            itemsElement.SetAttribute( "timestamp", timeNow );

            // metadata contains column names
            dbTableDescriptor* pTableMetaData = &RegistrationRow::dbDescriptor;

            do {
                // Create an item container
                TiXmlElement itemElement ("item");

                byte* base = (byte*)cursor.get();

                // Add the column name value pairs
                for ( dbFieldDescriptor* fd = pTableMetaData->getFirstField();
                      fd != NULL; fd = fd->nextField )
                {
                    // if the column name does not contain the
                    // np_prefix we must persist it
                    if ( strstr( fd->name, "np_" ) == NULL )
                    {
                        // Create a column element named after the IMDB column name
                        TiXmlElement element (fd->name );

                        // See if the IMDB has the predefined SPECIAL_NULL_VALUE
                        UtlString textValue;
                        SIPDBManager::getFieldValue(base, fd, textValue);

                        // If the value is not null append a text child element
                        if ( textValue != SPECIAL_IMDB_NULL_VALUE )
                        {
                            // Text type assumed here... @todo change this
                            TiXmlText value ( textValue.data() );
                            // Store the column value in the element making this
                            // <colname>coltextvalue</colname>
                            element.InsertEndChild  ( value );
                        }

                        // Store this in the item tag as follows
                        // <item>
                        // .. <col1name>col1textvalue</col1name>
                        // .. <col2name>col2textvalue</col2name>
                        // .... etc
                        itemElement.InsertEndChild  ( element );
                    }
                }
                // add the line to the element
                itemsElement.InsertEndChild ( itemElement );
            } while ( cursor.next() );
            // Attach the root node to the document
            document.InsertEndChild ( itemsElement );
            document.SaveFile ( fileName );
        }
        else
        {
            // database contains no rows so delete the file
            UtlString fileName =
                SIPDBManager::getInstance()->
                    getConfigDirectory() +
                    OsPath::separator + mDatabaseName + ".xml";
            if ( OsFileSystem::exists ( fileName ) ) {
                 OsFileSystem::remove( fileName );
            }
        }
    }
    else
    {
        result = OS_FAILED;
    }
    return result;
}

void
RegistrationDB::insertRow (const UtlHashMap& nvPairs)
{
    UtlString expStr = *((UtlString*)nvPairs.findValue(&gExpiresKey));
    int expires = (int) atoi( expStr );

    UtlString cseqStr = *((UtlString*)nvPairs.findValue(&gCseqKey));
    int cseq = (int) atoi( cseqStr );

    // If the IMDB does not specify a Q-Value % will be found here
    // (representing a null IMDB column)
    UtlString qvalue = *((UtlString*)nvPairs.findValue(&gQvalueKey));

    UtlString* updateNumberStr = dynamic_cast<UtlString*>(nvPairs.findValue(&gUpdateNumberKey));
    INT64 updateNumber = (updateNumberStr ? UtlLongLongInt::stringToLongLong(updateNumberStr->data()) : 0);

    // Get the remaining fields so that we can substitute the null string
    // if the fetched value is 0 (the null pointer) because the field
    // is not present in the disk file.
    UtlString* contact = (UtlString*) nvPairs.findValue(&gContactKey);
    UtlString* callId = (UtlString*) nvPairs.findValue(&gCallidKey);
    UtlString* instanceId = (UtlString*) nvPairs.findValue(&gInstanceIdKey);
    UtlString* gruu = (UtlString*) nvPairs.findValue(&gGruuKey);
    UtlString* primary = (UtlString*) nvPairs.findValue(&gPrimaryKey);

    // Note: identity inferred from the uri
    updateBinding (
        Url(*((UtlString*)nvPairs.findValue(&gUriKey))),
        *(contact ? contact : &nullString),
        qvalue,
        *(callId ? callId : &nullString),
        cseq,
        expires,
        *(instanceId ? instanceId : &nullString),
        *(gruu ? gruu : &nullString),
        *(primary ? primary : &nullString),
        updateNumber
        );
}


void
RegistrationDB::updateBinding(const RegistrationBinding& reg)
{
   updateBinding(*(reg.getUri()),    // must not be null
                 *(reg.getContact() ? reg.getContact() : &nullString),
                 *(reg.getQvalue() ? reg.getQvalue() : &nullString),
                 *(reg.getCallId() ? reg.getCallId() : &nullString),
                 reg.getCseq(),
                 reg.getExpires(),
                 *(reg.getInstanceId() ? reg.getInstanceId() : &nullString),
                 *(reg.getGruu() ? reg.getGruu() : &nullString),
                 *(reg.getPrimary() ? reg.getPrimary() : &nullString),
                 reg.getUpdateNumber());
}


void
RegistrationDB::updateBinding( const Url& uri
                              ,const UtlString& contact
                              ,const UtlString& qvalue
                              ,const UtlString& callid
                              ,const int& cseq
                              ,const int& expires
                              ,const UtlString& instance_id
                              ,const UtlString& gruu
                              ,const UtlString& primary
                              ,const INT64& update_number
                              )
{
    UtlString identity;
    uri.getIdentity(identity);
    UtlString fullUri = uri.toString();

    if ( !identity.isNull() && (m_pFastDB != NULL) )
    {
        SMART_DB_ACCESS;

        // Search for a matching row before deciding to update or insert
        dbCursor< RegistrationRow > cursor( dbCursorForUpdate );
        dbQuery query;

        query="np_identity=",identity,
            "  and contact=",contact;
        int existingBinding = cursor.select( query ) > 0;
        RegistrationRow row;
        switch ( existingBinding )
        {
            default:
                // Should not happen - there should either be 1 or none
                OsSysLog::add( FAC_SIP, PRI_ERR,
                              "RegistrationDB::updateBinding %d bindings for %s -> %s"
                              ,existingBinding, identity.data(), contact.data()
                              );
                // recover by clearing them out
                cursor.removeAllSelected();
                // and falling through to insert the new one

            case 0:
                // Insert new row
                row.np_identity   = identity;
                row.uri           = fullUri;
                row.callid        = callid;
                row.cseq          = cseq;
                row.contact       = contact;
                row.qvalue        = qvalue;
                row.expires       = expires;
                row.instance_id   = instance_id;
                row.gruu          = gruu;
                row.primary       = primary;
                row.update_number = update_number;
                insert (row);
                break;

            case 1:
                // this id->contact binding exists - update it
                cursor->uri           = fullUri;
                cursor->callid        = callid;
                cursor->cseq          = cseq;
                cursor->qvalue        = qvalue;
                cursor->expires       = expires;
                cursor->instance_id   = instance_id;
                cursor->gruu          = gruu;
                cursor->primary       = primary;
                cursor->update_number = update_number;
                cursor.update();
                break;
        }
    }
    else
    {
        OsSysLog::add( FAC_SIP, PRI_ERR,
                      "RegistrationDB::updateBinding bad state %s %p"
                      ,identity.data(), m_pFastDB
                      );
    }
}

/// clean out any bindings for this callid and older cseq values.
void
RegistrationDB::expireOldBindings( const Url& uri
                                  ,const UtlString& callid
                                  ,const int& cseq
                                  ,const int& timeNow
                                  ,const UtlString& primary
                                  ,const INT64& update_number
                                  )
{
    UtlString identity;
    uri.getIdentity(identity);

    int expirationTime = timeNow-1;

    if ( !identity.isNull() && ( m_pFastDB != NULL ) )
    {
        SMART_DB_ACCESS;
        dbCursor< RegistrationRow > cursor(dbCursorForUpdate);

        dbQuery query;
        query="np_identity=",identity,
           "and callid=",callid,
          "and cseq<",cseq,
          "and expires>=",expirationTime
          ;
        if (cursor.select(query) > 0)
        {
          do
            {
              cursor->expires = expirationTime;
              cursor->cseq = cseq;
              cursor->primary = primary;
              cursor->update_number = update_number;
              cursor.update();
            } while ( cursor.next() );
        }
    }
}

/// expireAllBindings for this URI as of 1 second before timeNow
void RegistrationDB::expireAllBindings( const Url& uri
                                       ,const UtlString& callid
                                       ,const int& cseq
                                       ,const int& timeNow
                                       ,const UtlString& primary
                                       ,const INT64& update_number
                                       )
{
    UtlString identity;
    uri.getIdentity(identity);
    int expirationTime = timeNow-1;

    if ( !identity.isNull() && ( m_pFastDB != NULL ) )
    {
        SMART_DB_ACCESS;
        dbCursor< RegistrationRow > cursor(dbCursorForUpdate);
        dbQuery query;
        query="np_identity=",identity," and expires>=",expirationTime;

        if (cursor.select(query) > 0)
        {
           do
           {
              cursor->expires = expirationTime;
              cursor->callid  = callid;
              cursor->cseq    = cseq;
              cursor->primary = primary;
              cursor->update_number = update_number;
              cursor.update();
           } while ( cursor.next() );
        }
    }
}

UtlBoolean
RegistrationDB::isOutOfSequence( const Url& uri
                                ,const UtlString& callid
                                ,const int& cseq
                                ) const
{
  UtlBoolean isOlder;

  UtlString identity;
  uri.getIdentity( identity );

  if ( !identity.isNull() && ( m_pFastDB != NULL) )
    {
      SMART_DB_ACCESS;
      dbCursor< RegistrationRow > cursor;
      dbQuery query;
      query="np_identity=",identity,
        " and callid=",callid,
        " and cseq>=",cseq;
      isOlder = ( cursor.select(query) > 0 );
    }
  else
    {
      OsSysLog::add( FAC_SIP, PRI_ERR,
                    "RegistrationDB::isOutOfSequence bad state @ %d %p"
                    ,__LINE__, m_pFastDB
                    );
      isOlder = TRUE; // will cause 500 Server Internal Error, which is true
    }

  return isOlder;
}


void
RegistrationDB::removeAllRows ()
{
    if ( m_pFastDB != NULL )
    {
        SMART_DB_ACCESS;
        dbCursor< RegistrationRow > cursor( dbCursorForUpdate );
        if (cursor.select() > 0)
        {
            cursor.removeAllSelected();
        }
    }
}

void
RegistrationDB::getAllRows ( ResultSet& rResultSet ) const
{
    // Clear out any previous records
    rResultSet.destroyAll();

    if ( m_pFastDB != NULL )
    {
        SMART_DB_ACCESS;
        dbCursor< RegistrationRow > cursor;
        if ( cursor.select() > 0 )
        {
            do {
                UtlHashMap record;
                UtlString* uriValue = new UtlString(cursor->uri);
                UtlString* callidValue = new UtlString(cursor->callid);
                UtlString* contactValue = new UtlString(cursor->contact);
                UtlInt* expiresValue = new UtlInt(cursor->expires);
                UtlInt* cseqValue = new UtlInt(cursor->cseq);
                UtlString* qvalueValue = new UtlString(cursor->qvalue);
                UtlString* primaryValue = new UtlString(cursor->primary);
                UtlLongLongInt* updateNumberValue = new UtlLongLongInt(cursor->update_number);

                // Memory Leak fixes, make shallow copies of static keys
                UtlString* uriKey = new UtlString(gUriKey);
                UtlString* callidKey = new UtlString(gCallidKey);
                UtlString* contactKey = new UtlString(gContactKey);
                UtlString* expiresKey = new UtlString(gExpiresKey);
                UtlString* cseqKey = new UtlString(gCseqKey);
                UtlString* qvalueKey = new UtlString(gQvalueKey);
                UtlString* primaryKey = new UtlString(gPrimaryKey);
                UtlString* updateNumberKey = new UtlString(gUpdateNumberKey);

                record.insertKeyAndValue(uriKey, uriValue);
                record.insertKeyAndValue(callidKey, callidValue);
                record.insertKeyAndValue(contactKey, contactValue);
                record.insertKeyAndValue(expiresKey, expiresValue);
                record.insertKeyAndValue(cseqKey, cseqValue);
                record.insertKeyAndValue(qvalueKey, qvalueValue);
                record.insertKeyAndValue(primaryKey, primaryValue);
                record.insertKeyAndValue(updateNumberKey, updateNumberValue);

                rResultSet.addValue(record);
            } while (cursor.next());
        }
    }
}

INT64
RegistrationDB::getMaxUpdateNumberForRegistrar(const UtlString& primaryRegistrar) const
{
   INT64 maxUpdateForPrimary = 0;

   if ( m_pFastDB != NULL )
   {
      SMART_DB_ACCESS;
      dbCursor<RegistrationRow> cursor;
      dbQuery query;
      query = "primary = ", primaryRegistrar, "order by update_number desc";
   
      int numRows = cursor.select(query);
      if (numRows > 0)
      {
         maxUpdateForPrimary = cursor->update_number;
      }
   }
   else
   {
      assert(false);    // when this method is called, the DB pointer should not be null
   }

   return maxUpdateForPrimary;
}

INT64
RegistrationDB::getNextUpdateNumberForRegistrar(const UtlString& primaryRegistrar,
                                                INT64            updateNumber) const
{
   INT64 nextUpdateNumber = 0;

   if ( m_pFastDB != NULL )
   {
      SMART_DB_ACCESS;
      dbCursor<RegistrationRow> cursor;
      dbQuery query;
      query = "primary = ", primaryRegistrar,
              " and update_number > ", updateNumber,
              " order by update_number asc";
   
      int numRows = cursor.select(query);
      if (numRows > 0)
      {
         nextUpdateNumber = cursor->update_number;
      }
   }
   else
   {
      assert(false);    // when this method is called, the DB pointer should not be null
   }

   return nextUpdateNumber;
}

int 
RegistrationDB::getNextUpdateForRegistrar(const UtlString& primaryRegistrar,
                                          INT64            updateNumber,
                                          UtlSList&        bindings) const
{
   int numRows = 0;
   INT64 nextUpdateNumber = getNextUpdateNumberForRegistrar(primaryRegistrar, updateNumber);
   if (nextUpdateNumber > 0)
   {
      dbQuery query;
      query =
         "primary = ", primaryRegistrar,
         " and update_number = ", nextUpdateNumber;
         numRows = getUpdatesForRegistrar(query, bindings);
      if (numRows > 0)
      {
         OsSysLog::add(
            FAC_SIP, PRI_DEBUG
            ,"RegistrationDB::getNextUpdateForRegistrar"
            " found %d rows for %s with updateNumber > %0#16llx"
            ,numRows
            ,primaryRegistrar.data()
            ,updateNumber);
      }   
   }
   return numRows;
}

int
RegistrationDB::getNewUpdatesForRegistrar(const UtlString& primaryRegistrar,
                                          INT64            updateNumber,
                                          UtlSList&        bindings) const
{
   dbQuery query;
   query = "primary = ", primaryRegistrar, " and update_number > ", updateNumber;
   int numRows = getUpdatesForRegistrar(query, bindings);
   if (numRows > 0)
   {
      OsSysLog::add(
         FAC_SIP, PRI_DEBUG
         ,"RegistrationDB::getNewUpdatesForRegistrar"
         " found %d rows for %s with updateNumber > %0#16llx"
         ,numRows
         ,primaryRegistrar.data()
         ,updateNumber);
   }   
   return numRows;
}

int
RegistrationDB::getUpdatesForRegistrar(dbQuery&  query,
                                       UtlSList& bindings) const
{
   int numRows = 0;
   if ( m_pFastDB != NULL )
   {
      SMART_DB_ACCESS;
      dbCursor<RegistrationRow> cursor(dbCursorForUpdate);
      numRows = cursor.select(query);
      if (numRows > 0)
      {
         do {
            RegistrationBinding* reg = copyRowToRegistrationBinding(cursor);
            bindings.append(reg);
         }
         while (cursor.next());
      }
   }
   return numRows;
}

void
RegistrationDB::getUnexpiredContacts (
   const Url& uri,
   const int& timeNow,
   ResultSet& rResultSet) const
{
    // Clear the results
    rResultSet.destroyAll();

    UtlString identity;
    uri.getIdentity( identity );

    if ( !identity.isNull() && ( m_pFastDB != NULL) )
    {
        SMART_DB_ACCESS;
        dbCursor< RegistrationRow > cursor;
        dbQuery query;
        OsSysLog::add(FAC_SIP, PRI_DEBUG,
                      "RegistrationDB::getUnexpiredContacts "
                      "identity = '%s'",
                      identity.data());
        if (strncmp(identity.data(), GRUU_PREFIX,
                    sizeof (GRUU_PREFIX) - 1) == 0)
        {
           // This is a GRUU, search for it in the gruu column.
           query="gruu=",identity," and expires>",timeNow;
           OsSysLog::add(FAC_DB, PRI_DEBUG,
                         "RegistrationDB::getUnexpiredContacts recognized GRUU");
        }
        else
        {
           // This is not a GRUU, search for it in the identity column.
           query="np_identity=",identity," and expires>",timeNow;
        }

        if ( cursor.select(query) > 0 )
        {
            // Copy all the unexpired contacts into the result hash
            do
            {
                UtlHashMap record;
                UtlString* uriValue = new UtlString(cursor->uri);
                UtlString* callidValue = new UtlString(cursor->callid);
                UtlString* contactValue = new UtlString(cursor->contact);
                UtlInt* expiresValue = new UtlInt(cursor->expires);
                UtlInt* cseqValue = new UtlInt(cursor->cseq);
                UtlString* qvalueValue = new UtlString(cursor->qvalue);
                UtlString* primaryValue = new UtlString(cursor->primary);
                UtlLongLongInt* updateNumberValue = new UtlLongLongInt(cursor->update_number);

                UtlString* instanceIdValue = new UtlString(cursor->instance_id);
                UtlString* gruuValue = new UtlString(cursor->gruu);
                OsSysLog::add(FAC_DB, PRI_DEBUG,
                              "RegistrationDB::getUnexpiredContacts Record found "
                              "uri = '%s', contact = '%s', instance_id = '%s', "
                              "gruu = '%s'",
                              uriValue->data(), contactValue->data(),
                              instanceIdValue->data(), gruuValue->data());

                // Memory Leak fixes, make shallow copies of static keys
                UtlString* uriKey = new UtlString(gUriKey);
                UtlString* callidKey = new UtlString(gCallidKey);
                UtlString* contactKey = new UtlString(gContactKey);
                UtlString* expiresKey = new UtlString(gExpiresKey);
                UtlString* cseqKey = new UtlString(gCseqKey);
                UtlString* qvalueKey = new UtlString(gQvalueKey);
                UtlString* primaryKey = new UtlString(gPrimaryKey);
                UtlString* updateNumberKey = new UtlString(gUpdateNumberKey);

                UtlString* instanceIdKey = new UtlString(gInstanceIdKey);
                UtlString* gruuKey = new UtlString(gGruuKey);

                record.insertKeyAndValue(uriKey, uriValue);
                record.insertKeyAndValue(callidKey, callidValue);
                record.insertKeyAndValue(contactKey, contactValue);
                record.insertKeyAndValue(expiresKey, expiresValue);
                record.insertKeyAndValue(cseqKey, cseqValue);
                record.insertKeyAndValue(qvalueKey, qvalueValue);
                record.insertKeyAndValue(primaryKey, primaryValue);
                record.insertKeyAndValue(updateNumberKey, updateNumberValue);
 
                record.insertKeyAndValue(instanceIdKey, instanceIdValue);
                record.insertKeyAndValue(gruuKey, gruuValue);

                rResultSet.addValue(record);

            } while ( cursor.next() );
        }
    }
}

RegistrationDB*
RegistrationDB::getInstance( const UtlString& name )
{
    // Critical Section here
    OsLock lock( sLockMutex );

    // See if this is the first time through for this process
    // Note that this being null => pgDatabase is also null
    if ( spInstance == NULL )
    {
        // Create the singleton class for clients to use
        spInstance = new RegistrationDB ( name );
    }
    return spInstance;
}

RegistrationBinding*
RegistrationDB::copyRowToRegistrationBinding(dbCursor<RegistrationRow>& cursor) const
{
   RegistrationBinding *reg = new RegistrationBinding();
   reg->setUri(* new UtlString(cursor->uri));
   reg->setCallId(* new UtlString(cursor->callid));
   reg->setContact(* new UtlString(cursor->contact));
   reg->setQvalue(* new UtlString(cursor->qvalue));
   reg->setInstanceId(* new UtlString(cursor->instance_id));
   reg->setGruu(* new UtlString(cursor->gruu));
   reg->setCseq(cursor->cseq);
   reg->setExpires(cursor->expires);
   reg->setPrimary(* new UtlString(cursor->primary));
   reg->setUpdateNumber(cursor->update_number);
   return reg;
}


syntax highlighted by Code2HTML, v. 0.9.1