// 
// 
// 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
//#include <...>

// APPLICATION INCLUDES
#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/SIPDBManager.h"
#include "sipdb/ResultSet.h"
#include "sipdb/CallerAliasRow.h"
#include "sipdb/CallerAliasDB.h"

#define CALLERALIAS_XML_NAMESPACE_URL "http://www.sipfoundry.org/sipX/schema/xml/caller-alias-00-00"

REGISTER( CallerAliasRow );

// STATIC INITIALIZERS
CallerAliasDB* CallerAliasDB::spInstance = NULL;
OsMutex  CallerAliasDB::sLockMutex (OsMutex::Q_FIFO);

const UtlString CallerAliasDB::IdentityKey("identity");
const UtlString CallerAliasDB::DomainKey("domain");
const UtlString CallerAliasDB::AliasKey("alias");

const UtlString CallerAliasDB::DbName("caller-alias");

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

CallerAliasDB::CallerAliasDB( const UtlString& name )
{
   // Access the shared table databse
   SIPDBManager* pSIPDBManager = SIPDBManager::getInstance();
   mpFastDB = 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();
   }
}

CallerAliasDB::~CallerAliasDB()
{
}

CallerAliasDB*
CallerAliasDB::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 CallerAliasDB( name );
   }
   return spInstance;
}

void
CallerAliasDB::releaseInstance()
{
   // 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 ( DbName );

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

      delete spInstance;
      spInstance = NULL;
   }
}

// Add a single mapping to the database.
void CallerAliasDB::insertRow(
   const UtlString identity, ///< identity of caller in 'user@domain' form (no scheme) 
   const UtlString domain,   /**< domain and optional port for target
                              *  ( 'example.com' or 'example.com:5099' ) */
   const UtlString alias     /// returned alias
                              )
{
   /*
    * The identity value may be the null string; this is a wildcard entry that matches
    * any caller to the given domain.
    */
   if ( !domain.isNull() && !alias.isNull() && (mpFastDB != NULL) )
   {
      // Thread Local Storage
      mpFastDB->attach();

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

      CallerAliasRow row;
      dbQuery query;

      // Primary Key is the urialias's identity
      query="identity=",identity.data(), " and domain=", domain.data();

      if ( cursor.select( query ) > 0 )
      {
         // Should only be one row so update the contact
         do {
            cursor->alias = alias.data();
            cursor.update();
         } while ( cursor.next() );
      }
      else // Insert as the row does not exist
      {
         // Fill out the row columns
         row.identity = identity.data();
         row.domain = domain.data();
         row.alias = alias.data();
         insert (row);
      }
#     if VERBOSE_LOGGING
      OsSysLog::add(FAC_DB,PRI_DEBUG,
                    "CallerAliasDB::insertRow "
                    "identity='%s', domain='%s', alias='%s'",
                    identity.data(), domain.data(), alias.data()
                    );
#     endif

      // Commit rows to memory - multiprocess workaround
      mpFastDB->detach(0);

      // Table Data changed
      SIPDBManager::getInstance()->
         setDatabaseChangedFlag(DbName, TRUE);
   }
   else
   {
      OsSysLog::add(FAC_DB,PRI_CRIT,
                    "CallerAliasDB::insertRow failed "
                    "db=%p, domain='%s', alias='%s'",
                    mpFastDB, domain.data(), alias.data()
                    );
   }
}

OsStatus
CallerAliasDB::load()
{
   // Critical Section here
   OsLock lock( sLockMutex );
   OsStatus result = OS_SUCCESS;

   if ( mpFastDB != NULL ) 
   {
      // Clean out the existing DB rows before loading
      // a new set from persistent storage
      removeAllRows ();

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

      OsSysLog::add(FAC_DB, PRI_DEBUG, "CallerAliasDB::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" ) )
            {
               UtlString identity;
               UtlString domain;
               UtlString alias;
                   
               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 column(elementNode->Value());

                     if (column.compareTo(IdentityKey) == 0)
                     {
                        SIPDBManager::getAttributeValue(*itemNode, column, identity);
                     }
                     else if (column.compareTo(DomainKey) == 0)
                     {
                        SIPDBManager::getAttributeValue(*itemNode, column, domain);
                     }
                     else if (column.compareTo(AliasKey) == 0)
                     {
                        SIPDBManager::getAttributeValue(*itemNode, column, alias);
                     }
                     else
                     {
                        OsSysLog::add(FAC_DB, PRI_ERR,
                                      "Unrecognized column '%s' in item: ignored",
                                      column.data()
                                      );
                     }
                  }
               }
               // Insert the item row into the IMDB
               insertRow (identity, domain, alias);
            }
         }
      }
      else 
      {
         OsSysLog::add(FAC_DB, PRI_WARNING, "CallerAliasDB::load failed to load '%s'",
                       fileName.data());
      }
   }
   else 
   {
      OsSysLog::add(FAC_DB, PRI_ERR, "CallerAliasDB::load failed - no DB");
      result = OS_FAILED;
   }
   return result;
}

OsStatus
CallerAliasDB::store()
{
   UtlString fileName = 
      SIPDBManager::getInstance()->
      getConfigDirectory() + 
      OsPath::separator + DbName + ".xml";

   // 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", DbName.data() );
   itemsElement.SetAttribute( "xmlns", CALLERALIAS_XML_NAMESPACE_URL );

   // Critical Section while actually opening and using the database
   {
      OsLock lock( sLockMutex );

      if ( mpFastDB != NULL ) 
      {
         // Thread Local Storage
         mpFastDB->attach();

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

         // Select everything in the IMDB and add as item elements if present
         int rowNumber;
         int rows;
         for (rowNumber = 0, rows = cursor.select();
              rowNumber < rows;
              rowNumber++, cursor.next()
              )
         {
            // Create an item container
            TiXmlElement itemElement ("item");

            if ( *cursor->identity )
            {
               // Add an identity element and put the value in it
               TiXmlElement identityElement(IdentityKey.data());
               TiXmlText    identityValue(cursor->identity);
               identityElement.InsertEndChild(identityValue);
               itemElement.InsertEndChild(identityElement);
            }
         
            // add the domain element and put the value in it
            TiXmlElement domainElement(DomainKey.data());
            TiXmlText    domainValue(cursor->domain);
            domainElement.InsertEndChild(domainValue);
            itemElement.InsertEndChild(domainElement);

            // add the alias element and put the value in it
            TiXmlElement aliasElement(AliasKey.data());
            TiXmlText    aliasValue(cursor->alias);
            aliasElement.InsertEndChild(aliasValue);
            itemElement.InsertEndChild(aliasElement);
            
            // add this item (row) to the parent items container
            itemsElement.InsertEndChild ( itemElement );
         }

         // Commit rows to memory - multiprocess workaround
         mpFastDB->detach(0);
      }
   } // release mutex around database use
   
   // Attach the root node to the document
   document.InsertEndChild ( itemsElement );
   document.SaveFile ( fileName );

   return OS_SUCCESS;
}


void
CallerAliasDB::removeAllRows ()
{
   // Thread Local Storage
   if (mpFastDB != NULL) 
   {
      mpFastDB->attach();

      dbCursor< CallerAliasRow > cursor(dbCursorForUpdate);

      if (cursor.select() > 0)
      {
         cursor.removeAllSelected();
      }
      // Commit rows to memory - multiprocess workaround
      mpFastDB->detach(0);

      // Table Data changed
      SIPDBManager::getInstance()->
         setDatabaseChangedFlag(DbName, TRUE);
   }
}


/// Get the caller alias for this combination of caller identity and target domain.
bool CallerAliasDB::getCallerAlias (
   const UtlString& identity, ///< identity of caller in 'user@domain' form (no scheme)
   const UtlString& domain,   /**< domain and optional port for target
                               *  ( 'example.com' or 'example.com:5099' ) */
   UtlString& callerAlias     /// returned alias
                     ) const
{
   /*
    * This first looks in the database for an exact match of identity and domain;
    *   if this match is found, the resulting alias is returned in callerAlias.
    * If no exact match is found, the database is then checked for a row containing
    *   a null (empty string) identity and the domain; this is a domain wildcard entry
    *   and it is returned in callerAlias.
    * If neither match is found, callerAlias is set to the null string.
    */
   callerAlias.remove(0);
   
   if (mpFastDB)
   {
      // Thread Local Storage
      mpFastDB->attach();

      // Search to see if we have a row with this exact combination
      dbQuery exactQuery;
      exactQuery="identity=",identity.data()," and domain=",domain.data();
      dbCursor< CallerAliasRow > exactCursor;
      if (exactCursor.select(exactQuery))
      {
         // found a match 
         callerAlias.append(exactCursor->alias);
      }
        
      // Commit the rows to memory - multiprocess workaround
      mpFastDB->detach(0);
   }

   // Returns true if an alias was found for this caller, false if not
   return ! callerAlias.isNull();
}




syntax highlighted by Code2HTML, v. 0.9.1