#include "PingableC.h"
#include "ior_bb.h"
#include "resource_key.h"
#include "entry.h"
#include "msg.h"

//////////////////////////////////////////////////////////////////////////
//
// Function: CORBA_obj_helper::get_object
//
// Purpose:
//
//   Get a reference to the named object, starting the appropriate server
//   if necessary.
//
// Description:
//
//    This convenience function acts as both a naming service and an
//    implementation repository for OPUS CORBA objects. The named CORBA 
//    object must be an OPUS::OPUS_API::Pingable or there must be
//    a Pingable object collocated in the same server with the object's
//    name concatenated with "/pingable/". Furthermore, there must be
//    a mapping for the object name to an object type in opus_corba_objs
//    with the appropriate command-line necessary to start the hosting
//    server.
//
//    This function first attempts to ping the named object (or its
//    collocated "/pingable/" object) and, if necessary, starts the
//    hosting server and waits for the object to go active. Once the
//    object responds, a narrowed reference to the object type is
//    returned to the caller.
//
//    The template parameter is the CORBA object type and the object
//    name should be the name of a CORBA object in the opus_iors file.
//    A mapping between object name and type, and the necessary server
//    command-line also must be present in opus_corba_objs.
//
// Returns:
//
//    A _ptr reference to the named object or _nil if a reference could not
//    be obtained. The caller must cleanup the returned reference.
//
// Exceptions thrown:
//
//    none
//
// Example:
//
//    using OPUS::OPUS_API::BB;
//    Blackboard_var bb = CORBA_obj_helper<Blackboard>("/home/foo/opus_home/");
//
// Modification History:
//
// Date      OPR      Who         Reason
// --------- -------- ----------- ----------------------------------------
// 01/08/02  44741    WMiller     Initial code
// 10/29/02  46765    Sontag      Standardizing for the gcc 3.2 compiler
// 07/29/03  41512    Sontag      Fix ambiguous implicit conversions
// 06/04/04  50605    Sontag      Handle all Opus_exceptions from ior_bb
// 06/11/04  49165    Sontag      Differentiate exception messages
// 06/18/04  51503    Sontag      Explicitly name retry constants
// 11/05/05  53872    Sontag      Handle race condition starting server
// 04/25/06  55700    Sontag      No naked endl; more info in exhausted msg
// 03/03/07  57369    Sontag      Use size_t to loop over collections
//////////////////////////////////////////////////////////////////////////

template<class T> typename T::_ptr_type CORBA_obj_helper::get_object(
             const std::string& id) // object name in opus_iors
{
   int retry_count = 0;
   Ior_bb ior_bb;
   std::string oid(id);
   std::string t_oid;
   CORBA::Object_var obj = T::_nil();
   bool not_pingable = false;
   bool had_to_start = false;
   bool called_start_obj_srvr = false;
   Entry* ent = 0;
   while(had_to_start || retry_count++ < CORBA_obj_helper::MAX_TRIES)
   {
      try {
         // This may throw an exception if locking gives up due to exhaustion
         obj = ior_bb.get_object(oid);
      }
      catch(Opus_exceptions& oe) {
         Msg m;
         m << sev(Msg::E) << type(Msg::SEVERE) <<
           "CORBA_obj_helper::get_object - An OPUS exception was thrown "
           "while checking IOR bb: " << oe.full() << endm;
         obj = T::_nil();
         sleep(CORBA_obj_helper::RETRY_INTERVAL);
         continue;
      }
      if (!CORBA::is_nil(obj.ptr())) {
         try {
            OPUS::OPUS_API::Pingable_var pingable = 
               OPUS::OPUS_API::Pingable::_narrow(obj.ptr());
            if (CORBA::is_nil(pingable.ptr())) {
               t_oid = oid;
               oid += "pingable/";
               not_pingable = true;
               continue;
            }
            pingable->ping();
            if (not_pingable) obj = ior_bb.get_object(t_oid);
            break;
         }
         catch(const CORBA::Exception& ce) {
            Msg m;
            m << sev(Msg::D) << "CORBA_obj_helper::get_object - During ping"
               " attempt: " << get_exception_text(ce) << endm;
         }
      }
      if (had_to_start) {
         sleep(CORBA_obj_helper::RETRY_INTERVAL);
         continue;
      }
      try {
         if (!called_start_obj_srvr) {
            delete ent;
            ent = ior_bb.new_ior_entry(id);
            called_start_obj_srvr = true; // only call it once, success or not
            had_to_start = start_object_server(ent); // can throw Bad_val
         }
         sleep(CORBA_obj_helper::RETRY_INTERVAL);
      }
      catch(const CORBA::Exception& e) {
         delete ent;
         Msg m;
         m << sev(Msg::E) << type(Msg::SEVERE) <<
            "CORBA_obj_helper::get_object - A CORBA exception was thrown: "
           << get_exception_text(e) << endm;
         return T::_nil();
      }
      catch(const Bad_val<std::string>& bv) {
         // 53872: Handle the race condition which occurs when multiple
         // processes try to start the same bb server.
         //
         // Even though we use the flag called_start_obj_srvr above, there
         // is still the rare case that process 'B' gets to the initial
         // ior_bb.get_object() line (above) before the server is started
         // and has its entry posted (from a *separate* process 'A' which
         // needed to start the same bb server), BUT, by the time 'B' gets
         // into the heart of start_object_server (above), the server has
         // completed startup and had its ./STARTING entry deleted (below).
         // In this case, 'B' thinks it can start the server, but when it
         // tries to post the entry to the opus_corba_objs file, it is a 
         // duplicate (since 'A' had already successfully started it).
         //
         // We can prove this case by catching Bad_val here and seeing if
         // ior_bb indeed has the object on it.

         obj = T::_nil();
         try {
            obj = ior_bb.get_object(id);
         }
         catch (...) {
            // don't care if this fails - only need to know if its successful
            obj = T::_nil();
         }
         if (!CORBA::is_nil(obj.ptr())) {
            Msg m;
            m << sev(Msg::I) << type(Msg::DUPL)
              << "CORBA_obj_helper::get_object - "
              << "Server has already been started, ignoring this Bad_val"
              << endm;
         } else {
            // this might be an actual Bad_val, so report it in full
            Msg m;
            m << sev(Msg::W) << type(Msg::DUPL)
              << "CORBA_obj_helper::get_object - Retrying after this Bad_val: "
              << bv.str() << endm;
            // allow to continue, if a real Bad_val, it will time out
         }
         sleep(CORBA_obj_helper::RETRY_INTERVAL);
         continue;
      }
      catch(Opus_exceptions& oe) {
         delete ent;
         Msg m;
         m << sev(Msg::E) << type(Msg::SEVERE) <<
            "CORBA_obj_helper::get_object - An OPUS exception was thrown: "
           << oe.full() << endm;
         return T::_nil();
      }
      catch(...) {
         delete ent;
         Msg m;
         m << sev(Msg::E) << type(Msg::SEVERE) <<
            "CORBA_obj_helper::get_object - An unknown exception was thrown."
           << endm;
         return T::_nil();
      }
   }

   // If we had to start it, then remove the .STARTING key, now that its up
   if (had_to_start) {
      Resource_bb* rbb = 0;
      std::vector<Entry*> res;
      try {
         rbb = new Resource_bb("OPUS_DEFINITIONS_DIR:opus_corba_objs");
         if (rbb->search(ent, res)) rbb->erase(res[0]);
         else {
            Msg m;
            m << sev(Msg::W) << type(Msg::MISSING) <<
              "CORBA_obj_helper::get_object - STARTING key missing for "
              << ent->str() << endm;
         }
         delete rbb;
         for (size_t i = 0; i < res.size(); ++i) delete res[i];
      }
      catch(Opus_exceptions& oe) {
         delete ent;
         delete rbb;
         for (size_t i = 0; i < res.size(); ++i) delete res[i];
         Msg m;
         m << sev(Msg::W) << type(Msg::SEVERE) <<
            "CORBA_obj_helper::get_object - An OPUS exception was thrown " 
            "while attempting to remove STARTING key: " << oe.full() << endm;
      }
   }

   delete ent;

   // Now return a narrowed reference to the object, if its valid
   if (CORBA::is_nil(obj.ptr())) {
      Msg m;
      m << sev(Msg::E) << type(Msg::SEVERE)
        << "CORBA_obj_helper::get_object - Exhausted "
        << CORBA_obj_helper::MAX_TRIES << " attempts to get object: "
        << id << ", at " << CORBA_obj_helper::RETRY_INTERVAL 
        << " second intervals." << endm;
      return T::_nil();
   }
   return T::_narrow(obj.ptr());
}
