// -*- Mode: C++; -*-
//                            Package   : omniORBpy
// pyObjectRef.cc             Created on: 1999/07/29
//                            Author    : Duncan Grisby (dpg1)
//
//    Copyright (C) 1999 AT&T Laboratories Cambridge
//
//    This file is part of the omniORBpy library
//
//    The omniORBpy library is free software; you can redistribute it
//    and/or modify it under the terms of the GNU Lesser General
//    Public License as published by the Free Software Foundation;
//    either version 2.1 of the License, or (at your option) any later
//    version.
//
//    This library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public
//    License along with this library; if not, write to the Free
//    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
//    MA 02111-1307, USA
//
//
// Description:
//    Versions of ORB object ref functions which deal with Python
//    objects, rather than C++ objects

// $Id: pyObjectRef.cc,v 1.1.4.7 2006/07/26 17:50:43 dgrisby Exp $
// $Log: pyObjectRef.cc,v $
// Revision 1.1.4.7  2006/07/26 17:50:43  dgrisby
// Reuse existing omniIOR object when converting C++ object reference to Python.
//
// Revision 1.1.4.6  2006/07/19 09:40:39  dgrisby
// Track ORB core changes.
//
// Revision 1.1.4.5  2006/05/15 10:26:11  dgrisby
// More relaxation of requirements for old-style classes, for Python 2.5.
//
// Revision 1.1.4.4  2005/06/24 17:36:01  dgrisby
// Support for receiving valuetypes inside Anys; relax requirement for
// old style classes in a lot of places.
//
// Revision 1.1.4.3  2005/04/25 18:27:41  dgrisby
// Maintain forwarded location when narrowing forwarded references.
//
// Revision 1.1.4.2  2005/01/07 00:22:32  dgrisby
// Big merge from omnipy2_develop.
//
// Revision 1.1.2.21  2004/04/05 09:06:42  dgrisby
// Bidirectional servers didn't work.
//
// Revision 1.1.2.20  2004/03/02 15:33:57  dgrisby
// Support persistent server id.
//
// Revision 1.1.2.19  2003/07/28 15:44:21  dgrisby
// Unlock interpreter during string_to_object.
//
// Revision 1.1.2.18  2003/03/14 15:28:43  dgrisby
// Use Python 1.5.2 sequence length function.
//
// Revision 1.1.2.17  2003/03/12 11:17:03  dgrisby
// Registration of external pseudo object creation functions.
//
// Revision 1.1.2.16  2002/08/02 13:33:49  dgrisby
// C++ API didn't allow ORB to be passed from C++ to Python, and required
// Python to have imported omniORB.
//
// Revision 1.1.2.15  2001/10/18 15:48:39  dpg1
// Track ORB core changes.
//
// Revision 1.1.2.14  2001/09/24 10:48:27  dpg1
// Meaningful minor codes.
//
// Revision 1.1.2.13  2001/09/20 14:51:25  dpg1
// Allow ORB reinitialisation after destroy(). Clean up use of omni namespace.
//
// Revision 1.1.2.12  2001/08/15 10:37:14  dpg1
// Track ORB core object table changes.
//
// Revision 1.1.2.11  2001/06/15 10:59:26  dpg1
// Apply fixes from omnipy1_develop.
//
// Revision 1.1.2.10  2001/06/11 13:06:26  dpg1
// Support for PortableServer::Current.
//
// Revision 1.1.2.9  2001/06/01 11:09:26  dpg1
// Make use of new omni::ptrStrCmp() and omni::strCmp().
//
// Revision 1.1.2.8  2001/05/29 17:10:14  dpg1
// Support for in process identity.
//
// Revision 1.1.2.7  2001/05/14 12:47:22  dpg1
// Fix memory leaks.
//
// Revision 1.1.2.6  2001/05/10 15:16:03  dpg1
// Big update to support new omniORB 4 internals.
//
// Revision 1.1.2.5  2001/03/13 10:38:07  dpg1
// Fixes from omnipy1_develop
//
// Revision 1.1.2.4  2001/01/10 12:00:07  dpg1
// Release the Python interpreter lock when doing potentially blocking
// stream calls.
//
// Revision 1.1.2.3  2000/12/04 18:57:23  dpg1
// Fix deadlock when trying to lock omniORB internal lock while holding
// the Python interpreter lock.
//
// Revision 1.1.2.2  2000/11/22 14:42:56  dpg1
// Fix segfault in string_to_object and resolve_initial_references with
// nil objref.
//
// Revision 1.1.2.1  2000/10/13 13:55:26  dpg1
// Initial support for omniORB 4.
//

#include <omnipy.h>
#include <omniORBpy.h>

// Internal omniORB interfaces
#include <objectTable.h>
#include <remoteIdentity.h>
#include <inProcessIdentity.h>
#include <objectAdapter.h>
#include <omniORB4/omniURI.h>
#include <giopStrand.h>
#include <giopStream.h>
#include <omniCurrent.h>
#include <poaimpl.h>

OMNI_USING_NAMESPACE(omni)

#if defined(HAS_Cplusplus_Namespace)
using omniORB::operator==;
#endif


class Py_omniObjRef : public virtual CORBA::Object,
		      public virtual omniObjRef
{
public:
  Py_omniObjRef(const char*        repoId,
		omniIOR*           ior,
		omniIdentity*      id)

    : omniObjRef(repoId, ior, id)
  {
    _PR_setobj(this);
  }
  virtual ~Py_omniObjRef() { }

  virtual const char* _localServantTarget();

private:
  virtual void* _ptrToObjRef(const char* target);

  // Not implemented:
  Py_omniObjRef(const Py_omniObjRef&);
  Py_omniObjRef& operator=(const Py_omniObjRef&);
};

const char*
Py_omniObjRef::_localServantTarget()
{
  return omniPy::string_Py_omniServant;
}

void*
Py_omniObjRef::_ptrToObjRef(const char* target)
{
  if (omni::ptrStrMatch(target, omniPy::string_Py_omniObjRef))
    return (Py_omniObjRef*)this;

  if (omni::ptrStrMatch(target, CORBA::Object::_PD_repoId))
    return (CORBA::Object_ptr)this;

  return 0;
}


PyObject*
omniPy::createPyCorbaObjRef(const char*             targetRepoId,
			    const CORBA::Object_ptr objref)
{
  if (CORBA::is_nil(objref)) {
    Py_INCREF(Py_None);
    return Py_None;
  }
  if (objref->_NP_is_pseudo())
    return createPyPseudoObjRef(objref);

  omniObjRef* ooref = objref->_PR_getobj();

  const char*    actualRepoId = ooref->_mostDerivedRepoId();
  PyObject*      objrefClass;
  CORBA::Boolean fullTypeUnknown = 0;

  // Try to find objref class for most derived type:
  objrefClass = PyDict_GetItemString(pyomniORBobjrefMap, (char*)actualRepoId);

  if (targetRepoId &&
      !omni::ptrStrMatch(targetRepoId, actualRepoId) &&
      !omni::ptrStrMatch(targetRepoId, CORBA::Object::_PD_repoId)) {

    // targetRepoId is not plain CORBA::Object, and is different from
    // actualRepoId

    if (objrefClass) {
      // We've got an objref class for the most derived type. Is it a
      // subclass of the target type?
      PyObject* targetClass = PyDict_GetItemString(pyomniORBobjrefMap,
						   (char*)targetRepoId);

      if (!omniPy::isSubclass(objrefClass, targetClass)) {
	// Actual type is not derived from the target. Surprisingly
	// enough, this is valid -- the repoId in an object reference
	// is not necessarily that of the most derived type for the
	// object. If we are expecting interface A, and actually get
	// unrelated B, the object might actually have interface C,
	// derived from both A and B.
	//
	// In this situation, we must create an object reference of
	// the target type, not the object's claimed type.
	objrefClass     = targetClass;
	fullTypeUnknown = 1;
      }
    }
    else {
      // No objref class for the most derived type -- try to find one for
      // the target type:
      objrefClass     = PyDict_GetItemString(pyomniORBobjrefMap,
					     (char*)targetRepoId);
      fullTypeUnknown = 1;
    }
  }
  if (!objrefClass) {
    // No target type, or stub code bug:
    objrefClass     = PyObject_GetAttrString(pyCORBAmodule, (char*)"Object");
    fullTypeUnknown = 1;
  }

  OMNIORB_ASSERT(objrefClass); // Couldn't even find CORBA.Object!

  PyObject* pyobjref = PyEval_CallObject(objrefClass, omniPy::pyEmptyTuple);

  if (!pyobjref) {
    // Oh dear -- return the error to the program
    return 0;
  }

  if (fullTypeUnknown) {
    PyObject* idstr = PyString_FromString(actualRepoId);
    PyObject_SetAttrString(pyobjref, (char*)"_NP_RepositoryId", idstr);
    Py_DECREF(idstr);
  }
  omniPy::setTwin(pyobjref, (CORBA::Object_ptr)objref, OBJREF_TWIN);

  return pyobjref;
}


PyObject*
omniPy::createPyPseudoObjRef(const CORBA::Object_ptr objref)
{
  {
    CORBA::ORB_var orbp = CORBA::ORB::_narrow(objref);
    if (!CORBA::is_nil(orbp)) {
      OMNIORB_ASSERT(omniPy::orb);
      return PyObject_GetAttrString(omniPy::pyomniORBmodule, (char*)"orb");
    }
  }
  {
    PortableServer::POA_var poa = PortableServer::POA::_narrow(objref);
    if (!CORBA::is_nil(poa)) return createPyPOAObject(poa);
  }
  {
    PortableServer::POAManager_var pm =
      PortableServer::POAManager::_narrow(objref);
    if (!CORBA::is_nil(pm)) return createPyPOAManagerObject(pm);
  }
  {
    PortableServer::Current_var pc = PortableServer::Current::_narrow(objref);
    if (!CORBA::is_nil(pc)) return createPyPOACurrentObject(pc);
  }
  do {
    // No built in converter. Try the list of registered external functions
    PyObject* fnlist = PyObject_GetAttrString(omniPy::py_omnipymodule,
					      (char*)"pseudoFns");
    if (!fnlist || !PySequence_Check(fnlist)) {
      PyErr_Clear();
      omniORB::logs(1, "WARNING: _omnipy.pseudoFns is not a sequence.");
      Py_XDECREF(fnlist);
      break;
    }
    int len = PySequence_Length(fnlist);
    for (int i=0; i < len; i++) {
      PyObject* pyf = PySequence_GetItem(fnlist, i);
      if (!PyCObject_Check(pyf)) {
	omniORB::logs(1, "WARNING: Entry in _omnipy.pseudoFns "
		      "is not a PyCObject.");
	continue;
      }
      omniORBpyPseudoFn f = (omniORBpyPseudoFn)PyCObject_AsVoidPtr(pyf);
      PyObject* ret = f(objref);
      if (ret) {
	Py_DECREF(fnlist);
	return ret;
      }
    }
    Py_DECREF(fnlist);

  } while (0);

  try {
    // Use OMNIORB_THROW to get a nice trace message
    OMNIORB_THROW(INV_OBJREF, INV_OBJREF_NoPythonTypeForPseudoObj,
		  CORBA::COMPLETED_NO);
  }
  OMNIPY_CATCH_AND_HANDLE_SYSTEM_EXCEPTIONS
  return 0;
}



omniObjRef*
omniPy::createObjRef(const char*    	targetRepoId,
		     omniIOR*       	ior,
		     CORBA::Boolean 	locked,
		     omniIdentity*  	id,
		     CORBA::Boolean     type_verified,
		     CORBA::Boolean     is_forwarded)
{
  ASSERT_OMNI_TRACEDMUTEX_HELD(*omni::internalLock, locked);
  OMNIORB_ASSERT(targetRepoId);
  OMNIORB_ASSERT(ior);

  if (!id) {
    ior->duplicate();  // consumed by createIdentity
    id = omni::createIdentity(ior, omniPy::string_Py_omniServant, locked);
    if (!id) {
      ior->release();
      return 0;
    }
  }

  if (omniORB::trace(10)) {
    omniORB::logger l;
    l << "Creating Python ref to ";
    if      (omniLocalIdentity    ::downcast(id)) l << "local";
    else if (omniInProcessIdentity::downcast(id)) l << "in process";
    else if (omniRemoteIdentity   ::downcast(id)) l << "remote";
    else                                          l << "unknown";
    l << ": " << id << "\n"
      " target id      : " << targetRepoId << "\n"
      " most derived id: " << (const char*)ior->repositoryID() << "\n";
  }

  omniObjRef* objref = new Py_omniObjRef(targetRepoId, ior, id);

  if (!type_verified &&
      !omni::ptrStrMatch(targetRepoId, CORBA::Object::_PD_repoId)) {

    objref->pd_flags.type_verified = 0;
  }

  if (is_forwarded) {
    omniORB::logs(10, "Reference has been forwarded.");
    objref->pd_flags.forward_location = 1;
  }

  {
    omni_optional_lock sync(*omni::internalLock, locked, locked);
    id->gainRef(objref);
  }

  if (orbParameters::persistentId.length()) {
    // Check to see if we need to re-write the IOR.

    omniIOR::IORExtraInfoList& extra = ior->getIORInfo()->extraInfo();

    for (CORBA::ULong index = 0; index < extra.length(); index++) {

      if (extra[index]->compid == IOP::TAG_OMNIORB_PERSISTENT_ID)

	if (!id->inThisAddressSpace()) {

	  omniORB::logs(15, "Re-write local persistent object reference.");

	  omniObjRef* new_objref;
	  omniIORHints hints(0);
	  {
	    omni_optional_lock sync(*internalLock, locked, locked);

	    omniIOR* new_ior = new omniIOR(ior->repositoryID(),
					   id->key(), id->keysize(), hints);

	    new_objref = createObjRef(targetRepoId, new_ior,
				      1, 0, type_verified);
	  }
	  releaseObjRef(objref);
	  objref = new_objref;
	}
      break;
    }
  }
  return objref;
}


omniObjRef*
omniPy::createLocalObjRef(const char*         mostDerivedRepoId,
			  const char*         targetRepoId,
			  omniObjTableEntry*  entry,
			  omniObjRef*         orig_ref,
			  CORBA::Boolean      type_verified)
{
  ASSERT_OMNI_TRACEDMUTEX_HELD(*omni::internalLock, 1);
  OMNIORB_ASSERT(targetRepoId);
  OMNIORB_ASSERT(entry);

  // See if a suitable reference exists in the local ref list.
  // Suitable means having the same most-derived-intf-repo-id, and
  // also supporting the <targetRepoId>.
  {
    omniObjRef* objref;

    omnivector<omniObjRef*>::iterator i    = entry->objRefs().begin();
    omnivector<omniObjRef*>::iterator last = entry->objRefs().end();

    for (; i != last; i++) {
      objref = *i;

      if (omni::ptrStrMatch(mostDerivedRepoId, objref->_mostDerivedRepoId()) &&
	  objref->_ptrToObjRef(omniPy::string_Py_omniObjRef) &&
	  omni::ptrStrMatch(targetRepoId, objref->pd_intfRepoId)) {

	// We just need to check that the ref count is not zero here,
	// 'cos if it is then the objref is about to be deleted!
	// See omni::releaseObjRef().

	omni::objref_rc_lock->lock();
	int dying = objref->pd_refCount == 0;
	if( !dying )  objref->pd_refCount++;
	omni::objref_rc_lock->unlock();

	if( !dying ) {
	  omniORB::logs(15, "omniPy::createLocalObjRef -- reusing "
			"reference from local ref list.");
	  return objref;
	}
      }
    }
  }
  // Reach here if we have to create a new objref.
  omniIOR* ior = orig_ref->_getIOR();
  return omniPy::createObjRef(targetRepoId, ior, 1, entry, type_verified);
}

omniObjRef*
omniPy::createLocalObjRef(const char* 	      mostDerivedRepoId,
			  const char* 	      targetRepoId,
			  const _CORBA_Octet* key,
			  int                 keysize,
			  omniObjRef*         orig_ref,
			  CORBA::Boolean      type_verified)
{
  ASSERT_OMNI_TRACEDMUTEX_HELD(*omni::internalLock, 1);
  OMNIORB_ASSERT(targetRepoId);
  OMNIORB_ASSERT(key && keysize);

  // See if there's a suitable entry in the object table
  CORBA::ULong hashv = omni::hash(key, keysize);

  omniObjTableEntry* entry = omniObjTable::locateActive(key, keysize,
							hashv, 0);

  if (entry)
    return createLocalObjRef(mostDerivedRepoId, targetRepoId,
			     entry, orig_ref, type_verified);

  omniIOR* ior = orig_ref->_getIOR();
  return createObjRef(targetRepoId,ior,1,0,type_verified);
}




CORBA::Object_ptr
omniPy::makeLocalObjRef(const char* targetRepoId,
			const CORBA::Object_ptr objref)
{
  ASSERT_OMNI_TRACEDMUTEX_HELD(*omni::internalLock, 0);

  omniObjRef* ooref = objref->_PR_getobj();
  omniObjRef* newooref;

  {
    omni_tracedmutex_lock sync(*omni::internalLock);
    
    omniObjTableEntry* entry = omniObjTableEntry::downcast(ooref->_identity());

    if (entry)
      newooref = omniPy::createLocalObjRef(ooref->_mostDerivedRepoId(),
					   targetRepoId, entry, ooref, 1);
    else
      newooref = omniPy::createLocalObjRef(ooref->_mostDerivedRepoId(),
					   targetRepoId,
					   ooref->_identity()->key(),
					   ooref->_identity()->keysize(),
					   ooref, 1);
  }
  return (CORBA::Object_ptr)newooref->_ptrToObjRef(CORBA::Object::_PD_repoId);
}


PyObject*
omniPy::copyObjRefArgument(PyObject* pytargetRepoId, PyObject* pyobjref,
			   CORBA::CompletionStatus compstatus)
{
  if (pyobjref == Py_None) {
    // Nil objref
    Py_INCREF(Py_None);
    return Py_None;
  }
  CORBA::Object_ptr objref = (CORBA::Object_ptr)getTwin(pyobjref, OBJREF_TWIN);
  if (!objref) {
    // Not an objref
    OMNIORB_THROW(BAD_PARAM, BAD_PARAM_WrongPythonType, compstatus);
  }

  // To copy an object reference, we have to take a number of things
  // into account. When the C++ object reference was created, it was
  // initialised with a most-derived repoId and a target repoId. If we
  // knew that the most-derived interface is compatible with the
  // target, then the Python objref is of the most derived type. If we
  // did not know the most-derived interface, or we did know it and
  // believed it to be incompatible with the target, then the Python
  // objref is of the target type, and it has a string attribute named
  // "_NP_RepositoryId" containing the most derived repoId.
  //
  // Now, as we are copying this objref, we have a target repoId,
  // which is possibly different from the objref's original target.
  // It's also possible that some time after we created the Python
  // objref, some new stubs were imported, so we now know about the
  // objref's most derived type when before we didn't.
  //
  // So, to copy the reference, we first see if the Python objref has
  // an attribute named "_NP_RepositoryId". If it does, all bets are
  // off, and we have to create a new C++ objref from scratch. If it
  // doesn't have the attribute, we look to see if the objref's class
  // is a subclass of the target objref class (or the same class). If
  // so, we can just incref the existing Python objref and return it;
  // if not, we have to build a new C++ objref.

  if (!PyObject_HasAttrString(pyobjref, (char*)"_NP_RepositoryId")) {

    PyObject* targetClass = PyDict_GetItem(pyomniORBobjrefMap,
					   pytargetRepoId);
    OMNIORB_ASSERT(targetClass);

    if (omniPy::isInstance(pyobjref, targetClass)) {
      Py_INCREF(pyobjref);
      return pyobjref;
    }
  }
  // Create new C++ and Python objrefs with the right target type
  omniObjRef* ooref        = objref->_PR_getobj();
  const char* targetRepoId = PyString_AS_STRING(pytargetRepoId);

  if (targetRepoId[0] == '\0') targetRepoId = CORBA::Object::_PD_repoId;

  omniObjRef* newooref;
  {
    omniPy::InterpreterUnlocker _u;
    newooref = omniPy::createObjRef(targetRepoId, ooref->_getIOR(), 0, 0);
  }
  PyObject* r = createPyCorbaObjRef(targetRepoId,
				    (CORBA::Object_ptr)newooref->
				      _ptrToObjRef(CORBA::Object::_PD_repoId));
  if (!r) {
    if (omniORB::trace(1)) {
      {
	omniORB::logger l;
	l <<
	  "Caught an unexpected Python exception trying to create an "
	  "object reference.\n";
      }
      PyErr_Print();
    }
    OMNIORB_THROW(INTERNAL, 0, compstatus);
  }
  return r;
}


CORBA::Object_ptr
omniPy::stringToObject(const char* uri)
{
  CORBA::Object_ptr cxxobj;
  omniObjRef* objref;

  {
    omniPy::InterpreterUnlocker _u;
    cxxobj = omniURI::stringToObject(uri);

    if (CORBA::is_nil(cxxobj) || cxxobj->_NP_is_pseudo()) {
      return cxxobj;
    }
    omniObjRef* cxxobjref = cxxobj->_PR_getobj();

    objref = omniPy::createObjRef(CORBA::Object::_PD_repoId,
				  cxxobjref->_getIOR(), 0, 0);
    CORBA::release(cxxobj);
  }
  return (CORBA::Object_ptr)objref->_ptrToObjRef(CORBA::Object::_PD_repoId);
}


CORBA::Object_ptr
omniPy::UnMarshalObjRef(const char* repoId, cdrStream& s)
{
  CORBA::String_var           id;
  IOP::TaggedProfileList_var  profiles;

  id = IOP::IOR::unmarshaltype_id(s);

  profiles = new IOP::TaggedProfileList();
  (IOP::TaggedProfileList&)profiles <<= s;

  if (profiles->length() == 0 && strlen(id) == 0) {
    // Nil object reference
    return CORBA::Object::_nil();
  }
  else {
    omniPy::InterpreterUnlocker _u;

    // It is possible that we reach here with the id string = '\0'.
    // That is alright because the actual type of the object will be
    // verified using _is_a() at the first invocation on the object.
    //
    // Apparently, some ORBs such as ExperSoft's do that. Furthermore,
    // this has been accepted as a valid behaviour in GIOP 1.1/IIOP 1.1.
    // 
    omniIOR* ior = new omniIOR(id._retn(),profiles._retn());

    giopStream* gs = giopStream::downcast(&s);
    if (gs) {
      giopStrand& g = (giopStrand&)*gs;
      if (g.biDir && !g.isClient()) {
	// Check the POA policy to see if the servant's POA is willing
	// to use bidirectional on its callback objects.
	omniCurrent* current = omniCurrent::get();
	omniCallDescriptor* desc = ((current)? current->callDescriptor() :0);

	if (desc && desc->poa() && desc->poa()->acceptBiDirectional()) {
	  const char* sendfrom = g.connection->peeraddress();
	  omniIOR::add_TAG_OMNIORB_BIDIR(sendfrom,*ior);
	}
      }
    }
    omniObjRef* objref = omniPy::createObjRef(repoId,ior,0);

    if (!objref) OMNIORB_THROW(MARSHAL, MARSHAL_InvalidIOR,
			       (CORBA::CompletionStatus)s.completion());
    return 
      (CORBA::Object_ptr)objref->_ptrToObjRef(CORBA::Object::_PD_repoId);
  }
  return 0; // To shut GCC up
}


syntax highlighted by Code2HTML, v. 0.9.1