#!/usr/bin/env python
#############################################################################
# Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1997, 1998, 1999
# All Rights Reserved.
#
# The software contained on this media is the property of the DSTC Pty
# Ltd.  Use of this software is strictly in accordance with the
# license agreement in the accompanying LICENSE.HTML file.  If your
# distribution of this software does not contain a LICENSE.HTML file
# then you have no rights to use this software in any manner and
# should contact DSTC at the address below to determine an appropriate
# licensing arrangement.
# 
#      DSTC Pty Ltd
#      Level 7, GP South
#      Staff House Road
#      University of Queensland
#      St Lucia, 4072
#      Australia
#      Tel: +61 7 3365 4310
#      Fax: +61 7 3365 4311
#      Email: enquiries@dstc.edu.au
# 
# This software is being provided "AS IS" without warranty of any
# kind.  In no event shall DSTC Pty Ltd be liable for damage of any
# kind arising out of or in connection with the use or performance of
# this software.
#
# Project:      Fnorb
# File:         $Source: /projects/fnorb/Fnorb-1.1/orb/RCS/BOA.py,v $
# Version:      @(#)$RCSfile: BOA.py,v $ $Revision: 1.32 $
#
#############################################################################
""" Implementation of the CORBA Basic Object Adapter (BOA). """


# Standard/built-in modules.
import regex, string

# Fnorb Modules.
import fnorb_thread, CORBA, IOP, Util

# BOA identifier.
BOA_ID = 'The Fnorb BOA v1.0.Threads.R.Us'


#############################################################################
# The BOA interface.
#############################################################################

def BOA_init(argv=[], boa_id=BOA_ID):
    """ Initialise the BOA.

    This is a factory function for the BOA class (the BOA is a singleton (ie.
    there can only be one BOA instance per process)).

    """
    try:
	boa = BOA(argv, boa_id)

    except BOA, boa:
	pass

    return boa


class BOA:
    """ The BOA object.

    The BOA is a singleton (ie. there can only be one BOA instance per
    process).

    """
    # Singleton instance.
    __instance = None

    # Mutex to make access via the factory function thread-safe.
    __lk = fnorb_thread.allocate_lock()

    def __init__(self, argv, boa_id):
	""" Constructor.

	'argv'   command line arguments.
	'boa_id' the unique identifier of the BOA implementation. This MUST
	         be the same string as the 'constant' BOA.BOA_ID.

	"""
	# The BOA id MUST be the same string as the 'constant' BOA_ID.
	if boa_id != BOA_ID:
	    raise CORBA.INITIALIZE() # System exception.

	# The BOA is a singleton (ie. there can only be one BOA instance per
	# process (have I made this point clear yet?!?).
	BOA.__lk.acquire()
	try:
	    if BOA.__instance is not None:
		raise BOA.__instance

	    BOA.__instance = self

	finally:
	    BOA.__lk.release()

	# Get a reference to the ORB that we are living in.
	orb = CORBA.ORB_init()

	# Register ourselves with the ORB.
	orb._fnorb_register_object_adapter(self)

	# Process any 'command line' options.
	self.__options = self.__get_options(argv)

	# If a particular hostname or port number was specified then use them.
	if self.__options.has_key('OAhost'):
	    host = self.__options['OAhost']

	else:
	    host = ''

	if self.__options.has_key('OAport'):
	    port = self.__options['OAport']

	else:
	    port = 0

	# To save a dictionary lookup on every operation invocation we set a
	# flag if the 'thread per request' option was specified.
	if self.__options.has_key('OAthread-per-request'):
	    self.__thread_per_request = 1

	else:
	    self.__thread_per_request = 0

	# A dictionary of active object implementations in the form:-
	# {ObjectKey: Implementation} with types {String: Instance}.
	self.__object_table = {}

	# A mutex for making access to the BOA's data structures thread-safe.
	self.__lk = fnorb_thread.allocate_lock()
	
	# Ask each protocol to create an endpoint on which to listen for
	# connection requests (no requests will actually be serviced until
	# we start the event loop).
	#
	# fixme: We will need some sort of abstraction for protocol
	# addresses. Hostname and port number are just kinda TCP/IP
	# specific!
	for protocol in orb._fnorb_protocols():
	    protocol.enable(host, port)

	return

    #########################################################################
    # BOA interface.
    #########################################################################

    def create(self, id, intrep_id):
	""" Generate an object reference.

	'id'        is the object reference data (aka the object key).
	'intrep_id' is the interface repository id of the interface.

        """
	# Create an IOR.
	ior = self._fnorb_create_ior(id, intrep_id)

	# Create an object reference from the IOR.
	return CORBA.ORB_init()._fnorb_ior_to_object(ior)

    def obj_is_ready(self, obj, impl):
	""" Inform the BOA that an object is prepared to accept requests.

	'obj'  is the object reference that the implementation will use.
	'impl' is the implementation repository object that implements the
	       interface (in our case, simply a Python instance!).
	
        """
	# Get the IOR from the object reference.
	ior = obj._fnorb_ior()

	# Get the object key from *any* protocol (there is only one object key
	# per IOR, not one per protocol!).
	for protocol in CORBA.ORB_init()._fnorb_protocols():
	    object_key = protocol.get_object_key(ior)
	    if object_key is not None:
		break

	else:
	    raise CORBA.INV_OBJREF() # System exception.

	# Add the implementation to our object table.
	self.__lk.acquire()
	self.__object_table[object_key] = impl
	self.__lk.release()

	# Add the object key to the implementation's key table.
	impl._fnorb_add_object_key(object_key)

	return

    def deactivate_obj(self, obj):
	""" Remove an object implementation from the BOA. """

	# Get the IOR from the object reference.
	ior = obj._fnorb_ior()

	# Get the object key from *any* protocol (there is only one object key
	# per IOR, not one per protocol!).
	for protocol in CORBA.ORB_init()._fnorb_protocols():
	    object_key = protocol.get_object_key(ior)
	    if object_key is not None:
		break

	else:
	    raise CORBA.INV_OBJREF() # System exception.

	self.__lk.acquire()
	try:
	    impl = self.__object_table[object_key]

	    # Remove the object key from the implementation's key table.
	    impl._fnorb_delete_object_key()

	    # Remove the object from the object table.
	    del self.__object_table[object_key]

	# Ignore attempts to delete an object that wasn't in the table in the
	# first place!
	except KeyError:
	    pass
	self.__lk.release()

	return

    #########################################################################
    # Fnorb-specific interface.
    #########################################################################

    def _fnorb_mainloop(self, timeout=None):
	""" Start the event loop. """

	# Get a reference to the ORB.
	orb = CORBA.ORB_init()

	# If the ORB is running in multi-threaded mode then start a thread-pool
	# to handle incoming requests.
	if orb._fnorb_threading_model() == Util.THREADED:
	    # Get the size of the thread pool from the ORB.
	    size = orb._fnorb_thread_pool_size()
	    if size > 0:
		from ThreadPoolQueue import ThreadPoolQueue

		# Create and start the queue.
		self.__queue = ThreadPoolQueue(size, self.__process_request)
		self.__queue.start()

	# Ladies and Gentlemen, start your engines...
	for protocol in CORBA.ORB_init()._fnorb_protocols():
	    # Ask the protocol to start its event loop.
	    protocol.start(timeout)
	    
	return

    def _fnorb_quit(self):
	""" Exit the event loop. """

	# Get a reference to the ORB.
	orb = CORBA.ORB_init()

	# Ok, that's enough...
	for protocol in orb._fnorb_protocols():
	    # Ask the protocol to stop its event loop.
	    protocol.stop()

	# If the ORB is running in multi-threaded mode then stop the thread
	# pool.
	if orb._fnorb_threading_model() == Util.THREADED:
	    self.__queue.stop()

	return

    def _fnorb_get_implementation(self, object_key):
	""" Return the implementation for the specified object key. """

	self.__lk.acquire()
	try:
	    impl = self.__object_table[object_key]

	except KeyError:
	    impl = None
	self.__lk.release()

	return impl

    def _fnorb_create_ior(self, object_key, intrep_id):
	""" Create an IOR.

	'id'        is the object reference data (aka the object key).
	'intrep_id' is the repository id of the object's interface.

        """
	profiles = []
	# Create a profile for each protocol.
	for protocol in CORBA.ORB_init()._fnorb_protocols():
	    profiles.append(protocol.create_profile(object_key))

	return IOP.IOR(intrep_id, profiles)

    def _fnorb_request(self, request):
	""" Process an operation request.

	Currently, the request is a tuple in the form:-

	(giop_server, request_header, cursor)

	"""
	if self.__thread_per_request:
	    fnorb_thread.start_new_thread(self.__process_request, (request,))

	else:
	    # Get a reference to the ORB.
	    orb = CORBA.ORB_init()

	    # If the ORB is running in multi-threaded mode and we are using a
	    # thread pool to service requests then simply put the request on
	    # the queue.
	    if orb._fnorb_threading_model() == Util.THREADED and \
	       orb._fnorb_thread_pool_size() > 0:
		self.__queue.add_item((request,))

	    # Otherwise, we handle the request immediately.
	    else:
		self.__process_request(request)

	return

    def _fnorb_object_here(self, object_key):
	""" Does the BOA contain an implementation for this object key? """

	# See if the object is here!
	self.__lk.acquire()
	object_here = self.__object_table.has_key(object_key)
	self.__lk.release()

	return object_here

    #########################################################################
    # Private interface.
    #########################################################################

    def __get_options(self, argv):
	""" Process 'command line' options. """

	# Work on a copy of the command line arguments.
	args = argv[:]
	
	# Command line arguments used by the BOA are stripped from the list.
	# We accomplish this by first emptying the list and then putting back
	# any non-BOA arguments.
	#
	# Empty the list without the destroying the list object itself.
	del argv[:]

	# For convenience we return any BOA options in a dictionary.
	options = {}

	i = 0
	while i < len(args):
	    arg = args[i]

	    if regex.match('--OAhost=.*', arg) != -1:
		value = arg[string.find(arg, '=') + 1:]
		options['OAhost'] = value

	    elif regex.match('--OAport=.*', arg) != -1:
		try:
		    value = arg[string.find(arg, '=') + 1:]
		    options['OAport'] = string.atoi(value)

		except ValueError:
		    raise CORBA.INITIALIZE() # System exception.

	    elif arg == '--OAthread-per-request':
		options['OAthread-per-request'] = None

	    else:
		argv.append(arg)

	    i = i + 1

	return options

    def __process_request(self, request):
	""" Handle a GIOP request. """

	# fixme: Need request abstraction?
	(giop_server, request_header, cursor) = request
	try:
	    self.__lk.acquire()
	    if self.__object_table.has_key(request_header.object_key):
		impl = self.__object_table[request_header.object_key]
	    else:
		impl = None
	    self.__lk.release()

	    # Handle any 'non-existent' operations (some ORBs use
	    # '_not_existent' instead of '_non_existent').
	    if request_header.operation in ['_non_existent', '_not_existent']:
		# If the implementation was found then it is NOT non-existent!
		result = impl is None

		# Send the appropriate reply.
		self.__non_existent(giop_server,request_header, cursor, result)

		return

	    # If the implementation is not here then raise an appropriate
	    # exception.
	    if impl is None:
		raise CORBA.NO_IMPLEMENT() # System exception.
	
	    # Look for the skeleton method on the implementation.
	    try:
		method = getattr(impl, '_skel_' + request_header.operation)

	    # If the skeleton method is not implemented then see if there is a
	    # Dynamic Invocation Routine (DIR).
	    #
	    # fixme: Should the DIR be __getattr__?
	    except AttributeError:
		try:
		    method = getattr(impl, 'invoke')

		# If there is no skeleton method and no DIR then the
		# implementation does not support this operation!
	        except AttributeError:
		    raise CORBA.NO_IMPLEMENT() # System exception.

	    # Add the object key to the implementation's key table.
	    impl._fnorb_add_object_key(request_header.object_key)

	    # Create a server request.
	    server_request = CORBA.ServerRequest(request_header, cursor)

	    # Invoke the skeleton method/DIR.
	    try:
		apply(method, (server_request,))

		# Send the reply (unless this was a 'oneway' request).
		if request_header.response_expected:
		    giop_server.reply(request_header, server_request)

	    except CORBA.UserException, ex:
		# Set the exception in the server request.
		server_request.exception(ex)

		# Notify the GIOP server of the user exception.
		giop_server.user_exception(request_header, server_request)

	except CORBA.SystemException, ex:
	    # Notify the GIOP server of the system exception.
	    giop_server.system_exception(request_header, ex)

	return

    def __non_existent(self, giop_server, request_header, cursor, result):
	""" Send a reply to a 'non_existent' request. """

	# Create a server request.
	server_request = CORBA.ServerRequest(request_header, cursor)

	# Initialise it.
	server_request.initialise([], [CORBA.TC_boolean], [])

	# Set the result.
	server_request.results(result)

	# Send the reply.
	giop_server.reply(request_header, server_request)

	return

#############################################################################
