#!/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/GIOPServer.py,v $
# Version:      @(#)$RCSfile: GIOPServer.py,v $ $Revision: 1.12 $
#
#############################################################################
""" GIOPServer class. """


# Fnorb modules.
import fnorb_thread, CORBA, GIOP, GIOPServerWorker, OctetStream, Util


class GIOPServer:
    """ GIOPServer class. """

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

	'connection' is a connection to a remote ORB.

	"""

	# A mutex to make access to the server thread-safe.
	self.__lk = fnorb_thread.allocate_lock()

	self.__lk.acquire()
	try:

	    # Get the object adapter used by the ORB.
	    #
	    # fixme: We get a reference to the object adapter here for the sake
	    # of efficiency, but is this dynamic enough?
	    #
	    # fixme: Should this be passed as a parameter to the constructor?
	    self.__oa = CORBA.ORB_init()._fnorb_object_adapter()

	    # Get a reference to the factory for GIOP server workers.
	    cwf = GIOPServerWorker.GIOPServerWorkerFactory_init()

	    # Create a new GIOP server worker (the concrete type of which will
	    # be determined by the threading model).
	    self.__worker = cwf.create_worker(self, connection)

	    # Flag set when our worker is closed.
	    self.__closed = 0

	finally:
	    self.__lk.release()

    def pseudo__del__(self):
	""" Pseudo destructor to remove circular references. """

	self.__lk.acquire()
	try:
	    # Remove the (circular) reference to our worker.
	    del self.__worker

	    # All done!
	    self.__closed = 1

	finally:
	    self.__lk.release()

    #########################################################################
    # GIOPServer interface.
    #########################################################################

    def process_giop_message(self, message):
	""" Process a GIOP message.

	This method is called by our worker when it has received a GIOP
	message.

	"""
	# Get a cursor for the message.
	cursor = message.cursor()

	# Get the GIOP message header.
	giop_header = message.header()

	# Make sure that the header has the right magic and that we are talking
	# the same version of GIOP.
	if giop_header.magic != Util.MAGIC \
	   or giop_header.GIOP_version.major != Util.GIOP_VERSION_MAJOR \
	   or giop_header.GIOP_version.minor != Util.GIOP_VERSION_MINOR:
	    self.__message_error()

	# Handle each GIOP message type.
	elif giop_header.message_type == GIOP.Request.value():
	    # Unmarshal the GIOP request header.
	    tc = CORBA.typecode('IDL:omg.org/GIOP/RequestHeader:1.0')
	    request_header = tc._fnorb_unmarshal_value(cursor)

	    # Pass the message to the active OA to invoke the operation on the
	    # appropriate object implementation. The OA will call one of
	    # our callback methods iff there is a reply to send.
	    self.__oa._fnorb_request((self, request_header, cursor))
		
	elif giop_header.message_type == GIOP.CancelRequest.value():
	    # fixme: We just ignore these at the moment!
	    pass

	elif giop_header.message_type == GIOP.LocateRequest.value():
	    # Unmarshal the GIOP locate request header.
	    tc = CORBA.typecode('IDL:omg.org/GIOP/LocateRequestHeader:1.0')
	    request_header = tc._fnorb_unmarshal_value(cursor)

	    # See if the object is here!
	    if self.__oa._fnorb_object_here(request_header.object_key):
		status = GIOP.OBJECT_HERE
	    else:
		status = GIOP.UNKNOWN_OBJECT

	    # Create and send the locate reply.
	    self.__locate_reply(request_header, status)

	elif giop_header.message_type == GIOP.MessageError.value():
	    # What can we do here?  Just ignore the message!
	    pass

	# Some crappy message or other ;^)
	else:
	    self.__message_error()
    
	return

    def close_connection(self):
	""" Close down the connection.

	Currently, Fnorb does not close down servers, so this is not used.

	"""
 	# Send the close connection message.
 	self.__close_connection()

	# In multi-thread mode, if the client has disconnected then the worker
	# will be 'None'.
	worker = self.__get_worker()
	if worker is not None:
	    worker.close_connection()

	return
	
    #########################################################################
    # Object adapter callback interface.
    #########################################################################

    def reply(self, request_header, server_request):
	""" Create and send a successful reply message. """

	# Start the reply.
	reply = OctetStream.GIOPMessage(type=GIOP.Reply)
	cursor = reply.cursor()

	# Create the reply header.
	reply_header = GIOP.ReplyHeader(request_header.service_context,
					request_header.request_id,
					GIOP.NO_EXCEPTION)

	# Marshal it onto the octet stream.
	tc = CORBA.typecode('IDL:omg.org/GIOP/ReplyHeader:1.0')
	tc._fnorb_marshal_value(cursor, reply_header)

	# Marshal the results onto the octet stream.
	self.__marshal_results(cursor,
			       server_request.outputs(),
			       server_request._fnorb_results())
	# Send it!
	self.__send(reply)

	return

    def user_exception(self, request_header, server_request):
	""" Create and send a user exception reply message. """

	# Start the reply.
	reply = OctetStream.GIOPMessage(type=GIOP.Reply)
	cursor = reply.cursor()

	# Create the reply header.
	reply_header = GIOP.ReplyHeader(request_header.service_context,
					request_header.request_id,
					GIOP.USER_EXCEPTION)

	# Marshal it onto the octet stream.
	tc = CORBA.typecode('IDL:omg.org/GIOP/ReplyHeader:1.0')
	tc._fnorb_marshal_value(cursor, reply_header)

	# Get the exception that was raised in response to the server request.
	ex = server_request._fnorb_exception()

	# Try to find a matching exception.
	for typecode in server_request.exceptions():
	    if ex._FNORB_ID == typecode.id():
		break

	# If there is no matching exception then raise an 'UNKNOWN' system
	# exception.
	else:
	    raise CORBA.UNKNOWN() # System exception.

	# Marshal the repository id of the exception followed by its members.
	cursor.marshal('s', typecode.id())
	typecode._fnorb_marshal_value(cursor, ex)

	# Send it.
	self.__send(reply)

	return
    
    def system_exception(self, request_header, ex):
	""" Create and send a system exception reply message. """

	# Create an octet stream and get a cursor for it.
	reply = OctetStream.GIOPMessage(type=GIOP.Reply)
	cursor = reply.cursor()

	# Create the reply header.
	reply_header = GIOP.ReplyHeader(request_header.service_context,
					request_header.request_id,
					GIOP.SYSTEM_EXCEPTION)

	# Marshal it onto the octet stream.
	tc = CORBA.typecode('IDL:omg.org/GIOP/ReplyHeader:1.0')
	tc._fnorb_marshal_value(cursor, reply_header)

	# Marshal the system exception onto the octet stream.
	cursor.marshal('s', "IDL:omg.org/CORBA/%s:1.0" % ex.__class__.__name__)
	ex._fnorb_marshal(cursor)

	# Send it.
	self.__send(reply)

	return

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

    def __get_worker(self):
	""" Get our GIOP server worker. """

	self.__lk.acquire()
	try:
	    if self.__closed:
		worker = None

	    else:
		worker = self.__worker

	finally:
	    self.__lk.release()

  	return worker

    def __send(self, message):
	""" Send a GIOP message via our worker. """

	# In multi-thread mode, if the client has disconnected before we have
	# had a chance to send the reply, then the worker will be 'None'.
	worker = self.__get_worker()
	if worker is not None:
	    worker.send(message)

	return

    def __marshal_results(self, cursor, typecodes, results):
	""" Marshal the results of a request onto an octet stream. """

	no_of_outputs = len(typecodes)

	# If there are no outputs then do nothing ;^)
	if no_of_outputs == 0:
	    pass

	# If there is exactly 1 result from the request then 'results'
	# contains a single value.
	elif no_of_outputs == 1:
	    # Marshal the first and only output!
	    typecodes[0]._fnorb_marshal_value(cursor, results)

	# Otherwise, 'results' is a tuple containing all of the results.
	else:
	    i = 0
	    for typecode in typecodes:
		typecode._fnorb_marshal_value(cursor, results[i])
		i = i + 1

	return

    def __locate_reply(self, request_header, status):
	""" Create and send a locate reply. """

	# Start the reply.
	message = OctetStream.GIOPMessage(type=GIOP.LocateReply)
	cursor = message.cursor()

	# Create the locate reply header and marshal it onto the octet stream.
	reply_header = GIOP.LocateReplyHeader(request_header.request_id,status)
	tc = CORBA.typecode('IDL:omg.org/GIOP/LocateReplyHeader:1.0')
	tc._fnorb_marshal_value(cursor, reply_header)

	# Send it!
	self.__send(message)

	return

    def __message_error(self):
	""" Create and send a 'MessageError' message. """

	# Create the 'MessageError' message.
	message = OctetStream.GIOPMessage(type=GIOP.MessageError)

	# Send it!
	self.__send(message)

	return

    def __close_connection(self):
	""" Create and send a close connection message. """

	# Create the 'CloseConnection' message.
	message = OctetStream.GIOPMessage(type=GIOP.CloseConnection)

	# Send it!
	self.__send(message)

	return

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