#!/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/cos/naming/RCS/NamingContext.py,v $
# Version:      @(#)$RCSfile: NamingContext.py,v $ $Revision: 1.11 $
#
#############################################################################
""" COS Naming Service Naming Contexts. """


# Standard/built-in modules.
import string

# Fnorb modules.
from Fnorb.orb import fnorb_thread, uuid, BOA, CORBA

# Stubs and skeletons.
import CosNaming, CosNaming_skel

# Naming service modules.
import BindingIterator


def NamingContextFactory_init():
    """ Initialise the Naming Contect Factory.

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

    """
    try:
	ncf = NamingContextFactory()

    except NamingContextFactory, ncf:
	pass

    return ncf


class NamingContextFactory:
    """ A factory for naming contexts!

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

    """
    # Singleton instance.
    __instance = None

    def __init__(self):
	""" Constructor. """

	# The factory is a singleton (ie. there can only be one instance per
	# process).
	if NamingContextFactory.__instance is not None:
	    raise NamingContextFactory.__instance

	NamingContextFactory.__instance = self

	return

    def create_naming_context(self, object_key=None):
	""" Create and return a new naming context. """

	# Create an object key for the context.
	if object_key is None:
	    # Generate a unique object key for the context.
	    object_key = uuid.uuid()

	# Create an instance of the implementation class.
	impl = NamingContext()

	# Create an object reference.
	boa = BOA.BOA_init()
	nc = boa.create(object_key, NamingContext._FNORB_ID)

	# Activate the implementation (ie. connect the generated object
	# reference to the implementation).
	boa.obj_is_ready(nc, impl)

	return impl


class NamingContext(CosNaming_skel.NamingContext_skel):
    """ Naming context implementation. """

    # A binding iterator factory that is shared by every context instance.
    bif = BindingIterator.BindingIteratorFactory_init()

    def __init__(self, nc_factory=None, d_bindings=None):
	""" Constructor. """

	# A factory responsible for creating new naming contexts.
	if nc_factory is None:
	    self.__nc_factory = NamingContextFactory_init()

	else:
	    self.__nc_factory = nc_factory

	# A dictionary containing the bindings in the context, in the form:-
	# {(NameComponentId, NameComponentKind): (Binding, StringifiedIOR)}.
	#
	# We cannot key the dictionary directly on a NameComponent instance
	# because the NameComponent class is generated automatically by the
	# IDL compiler and therefore has no '__hash__' method.
	if d_bindings is None:
	    self.__d_bindings = {}

	else:
	    self.__d_bindings = d_bindings

	# A mutex to make access to the binding dictionary safe in threaded
	# environments.
	self.__lk = fnorb_thread.allocate_lock()

	return

    #########################################################################
    # CORBA interface.
    #########################################################################

    def bind(self, n, obj):
	""" Bind the name 'n' to the object reference obj. """

	# An 'empty' name is invalid.
	if len(n) == 0:
	    raise CosNaming.NamingContext.InvalidName()

	# Get the first component of the name.
	component = n[0]

	# The binding dictionary is keyed on (ComponentId, ComponentKind).
	key = str((component.id, component.kind))

	# If there is exactly one component in the name then the operation
	# takes place in *this* context.
	if len(n) == 1:
	    self.__lk.acquire()
	    try:
		# Is the component already bound?
		if self.__d_bindings.has_key(key):
		    raise CosNaming.NamingContext.AlreadyBound()

		# Create the binding.
		binding = CosNaming.Binding(n, CosNaming.nobject)

		# Stringify the object reference.
		orb = CORBA.ORB_init()
		stringified_ior = orb.object_to_string(obj)

		# Add it to the binding dictionary.
		self.__d_bindings[key] = (binding, stringified_ior)

	    finally:
		self.__lk.release()

	# Otherwise, attempt to continue resolution into the next context.
	else:
	    self.__resolve('bind', key, n, obj)

	return

    def rebind(self, n, obj):
	""" Rebind the name 'n' to the object reference obj. """

	# An 'empty' name is invalid.
	if len(n) == 0:
	    raise CosNaming.NamingContext.InvalidName()

	# Get the first component of the name.
	component = n[0]

	# The binding dictionary is keyed on (ComponentId, ComponentKind).
	key = str((component.id, component.kind))

	# If there is exactly one component in the name then the operation
	# takes place in *this* context.
	if len(n) == 1:
	    # Create the binding.
	    binding = CosNaming.Binding(n, CosNaming.nobject)

	    # Stringify the object reference.
	    orb = CORBA.ORB_init()
	    stringified_ior = orb.object_to_string(obj)

	    # Add it to the binding dictionary.
	    self.__lk.acquire()
	    self.__d_bindings[key] = (binding, stringified_ior)
	    self.__lk.release()

	# Otherwise, attempt to continue resolution into the next context.
	else:
	    self.__resolve('rebind', key, n, obj)

	return

    def bind_context(self, n, nc):
	""" Bind the name 'n' to the naming context nc. """

	# An 'empty' name is invalid.
	if len(n) == 0:
	    raise CosNaming.NamingContext.InvalidName()

	# Get the first component of the name.
	component = n[0]

	# The binding dictionary is keyed on (ComponentId, ComponentKind).
	key = str((component.id, component.kind))

	# If there is exactly one component in the name then the operation
	# takes place in *this* context.
	if len(n) == 1:
	    self.__lk.acquire()
	    try:
		# Is the component already bound?
		if self.__d_bindings.has_key(key):
		    raise CosNaming.NamingContext.AlreadyBound()

		# Create the binding.
		binding = CosNaming.Binding(n, CosNaming.ncontext)

		# Stringify the object reference.
		orb = CORBA.ORB_init()
		stringified_ior = orb.object_to_string(nc)

		# Add it to the binding dictionary.
		self.__d_bindings[key] = (binding, stringified_ior)

	    finally:
		self.__lk.release()

	# Otherwise, attempt to continue resolution into the next context.
	else:
	    self.__resolve('bind_context', key, n, nc)

	return

    def rebind_context(self, n, nc):
	""" Rebind the name 'n' to the naming context nc. """

	# An 'empty' name is invalid.
	if len(n) == 0:
	    raise CosNaming.NamingContext.InvalidName()

	# Get the first component of the name.
	component = n[0]

	# The binding dictionary is keyed on (ComponentId, ComponentKind).
	key = str((component.id, component.kind))

	# If there is exactly one component in the name then the operation
	# takes place in *this* context.
	if len(n) == 1:
	    # Create the binding.
	    binding = CosNaming.Binding(n, CosNaming.ncontext)

	    # Stringify the object reference.
	    orb = CORBA.ORB_init()
	    stringified_ior = orb.object_to_string(nc)

	    # Add it to the binding dictionary.
	    self.__lk.acquire()
	    self.__d_bindings[key] = (binding, stringified_ior)
	    self.__lk.release()

	# Otherwise, attempt to continue resolution into the next context.
	else:
	    self.__resolve('rebind_context', key, n, nc)

	return

    def resolve(self, n):
	""" Resolve the name 'n'. """

	# An 'empty' name is invalid.
	if len(n) == 0:
	    raise CosNaming.NamingContext.InvalidName()

	# Get the first component of the name.
	component = n[0]

	# The binding dictionary is keyed on (ComponentId, ComponentKind).
	key = str((component.id, component.kind))

	# If there is exactly one component in the name then the operation
	# takes place in *this* context.
	if len(n) == 1:
	    self.__lk.acquire()
	    try:
		# Is the component bound in this context?
		if not self.__d_bindings.has_key(key):
		    raise CosNaming.NamingContext.NotFound \
			  (CosNaming.NamingContext.missing_node, n)

		# Get the stringified IOR bound to the component.
		(binding, stringified_ior) = self.__d_bindings[key]

		# Convert the stringified IOR into an active object reference.
		orb = CORBA.ORB_init()
		result = orb.string_to_object(stringified_ior)

	    finally:
		self.__lk.release()
	    
	# Otherwise, attempt to continue resolution into the next context.
	else:
	    result = self.__resolve('resolve', key, n)

	return result

    def unbind(self, n):
	""" Unbind the name 'n'. """

	# An 'empty' name is invalid.
	if len(n) == 0:
	    raise CosNaming.NamingContext.InvalidName()

	# Get the first component of the name.
	component = n[0]

	# The binding dictionary is keyed on (ComponentId, ComponentKind).
	key = str((component.id, component.kind))

	# If there is exactly one component in the name then the operation
	# takes place in *this* context.
	if len(n) == 1:
	    self.__lk.acquire()
	    try:
		# Is the component bound in this context?
		if not self.__d_bindings.has_key(key):
		    raise CosNaming.NamingContext.NotFound \
                          (CosNaming.NamingContext.missing_node, n)

		# Delete the binding.
		del self.__d_bindings[key]

	    finally:
		self.__lk.release()

	# Otherwise, attempt to continue resolution into the next context.
	else:
	    self.__resolve('unbind', key, n)

	return

    def new_context(self):
	""" Create a new naming context. """

	# Ask the factory to create a new naming context for us.
	return self.__nc_factory.create_naming_context()

    def bind_new_context(self, n):
	""" Create a new naming context and bind it to the name 'n'. """

	# An 'empty' name is invalid.
	if len(n) == 0:
	    raise CosNaming.NamingContext.InvalidName()

	# Get the first component of the name.
	component = n[0]

	# The binding dictionary is keyed on (ComponentId, ComponentKind).
	key = str((component.id, component.kind))

	# If there is exactly one component in the name then the operation
	# takes place in *this* context.
	if len(n) == 1:
	    self.__lk.acquire()
	    try:
		# Is the component already bound?
		if self.__d_bindings.has_key(key):
		    raise CosNaming.NamingContext.AlreadyBound()

		# Create the binding.
		binding = CosNaming.Binding(n, CosNaming.ncontext)

		# Create a new context.
		nc = self.new_context()

		# Stringify the object reference.
		orb = CORBA.ORB_init()
		stringified_ior = orb.object_to_string(nc)

		# Add it to the binding dictionary.
		self.__d_bindings[key] = (binding, stringified_ior)
	    
	    finally:
		self.__lk.release()

	# Otherwise, attempt to continue resolution into the next context.
	else:
	    nc = self.__resolve('bind_new_context', key, n)

	return nc

    def destroy(self):
	""" Destroy the naming context. """

	self.__lk.acquire()
	try:
	    # Naming contexts must be empty to be destroyed!
	    if len(self.__d_bindings.keys()) > 0:
		raise CosNaming.NamingContext.NotEmpty()

	finally:
	    self.__lk.release()

	# Unregister myself from the BOA.
	boa = BOA.BOA_init()
	boa.deactivate_obj(self)

	return
	
    def list(self, how_many):
	""" List the bindings in a context. """

	# Create a list of the bindings in the context.
	#
	# Note that we do not use 'self.__d_bindings.values()' to get at the
	# dictionary contents; this is a small concession to allow Python
	# shelves to be used for creating persistent contexts (shelves do not
	# implement the 'values' method).
	self.__lk.acquire()
	bl = []
	for key in self.__d_bindings.keys():
	    (binding, obj) = self.__d_bindings[key]
	    bl.append(binding)
	self.__lk.release()

	# If there are more bindings than have been requested, then return a
	# binding iterator.
	if len(bl) > how_many:
	    iterator = NamingContext.bif.create_binding_iterator(bl[how_many:])

	# Otherwise return a nil reference for the iterator.
	else:
##	    orb = CORBA.ORB_init()
##	    iterator = orb.nil()
	    iterator = None

	return (bl[:how_many], iterator)

    #########################################################################
    # Internal interface.
    #########################################################################

    def __resolve(self, op, key, n, *args):
	""" Resolve a naming operation through a naming graph. """

	self.__lk.acquire()
	try:
	    # If the component is not bound in this context then we can go no
	    # further!
	    if not self.__d_bindings.has_key(key):
		raise CosNaming.NamingContext.NotFound \
		      (CosNaming.NamingContext.missing_node, n)

	    # Get the binding!
	    (binding, stringified_ior) = self.__d_bindings[key]

	finally:
	    self.__lk.release()

	# If the binding contains another naming context then continue the
	# resolution.
	if binding.binding_type == CosNaming.ncontext:
	    # Convert the stringified IOR into an active object reference.
	    orb = CORBA.ORB_init()
	    next = orb.string_to_object(stringified_ior)

	    # Find the appropriate method on the context.
	    method = getattr(next, op)

	    # Invoke the method translating CORBA system exceptions into
	    # 'CannotProceed' exceptions.
	    try:
		result = apply(method, (n[1:],) + args)

	    except CORBA.SystemException:
		raise CosNaming.NamingContext.CannotProceed(self, n)

	# Otherwise, the binding contains some other object reference and
	# therefore we cannot continue the resolution.
	else:
	    raise CosNaming.NamingContext.NotFound \
		  (CosNaming.NamingContext.not_context, n)

	return result

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