########################################################################
#
# File Name:    FtRpcHandler.py
#
"""

WWW: http://4suite.org                  e-mail: support@4suite.org

Copyright 2001-2002 Fourthought, Inc., USA.
See  http://4suite.org/COPYRIGHT  for license and copyright information
"""

from Ft.Server.Common import ResourceTypes, Schema
from Ft.Server.FtRpc import Marshal, Commands, Responses
from Ft.Server.Server import SCore, RequestHandler
from Ft.Server.Server.SCore import ResourceMetaDataImp

from Ft.Server.Server import FtServerServerException, Error

from Ft.Server.FtRpc import FtServerFtRpcException
from Ft.Server.FtRpc import Error as RpcError


commands = Commands.g_commandMapping.copy()

class FtRpcHandler(RequestHandler.RequestHandler):

    def handle(self):
        errorLog = self.server.errorLog
        self._repo = None
        try:
            done = 0
            while not done:
                try:
                    # Get the message type and keyword args sent by the client
                    code, args = Marshal.Receive(self.connection.client)
                    if code not in Commands.g_commandMapping.keys():
                        raise FtServerFtRpcException(RpcError.UNKNOWN_COMMAND,
                                                     cmdCode=code)
                    
                    # Recreate the command object using the args as keywords
                    # to the contructor.
                    command = Commands.g_commandMapping[code](**args)
                    
                    # Process the command
                    done, response = self._commandMapping[code](self, command)
                except FtServerServerException, error:
                    response = Responses.FtServerErrorResponse(error)
                except FtServerFtRpcException, error:
                    # This is here so as not to be sent as a general error.
                    raise
                except Exception, error:
                    # Unknown exception, send it to the client and die
                    import cStringIO, traceback
                    file = cStringIO.StringIO()
                    traceback.print_exc(file=file)
                    tb = file.getvalue()
                    errorLog.error('RPC: unknown exception:\n%s' % tb)
                    response = Responses.GeneralErrorResponse(error, tb)
                    done = 1

                # Send the results back to the client
                response.send(self.connection.client)

        except FtServerFtRpcException:
            # The protocol got itself it trouble (malformed data, connection
            # dropped, ...).  Just log it and terminate since we cannot
            # assume the connection is valid.
            import cStringIO, traceback
            file = cStringIO.StringIO()
            traceback.print_exc(file=file)
            errorLog.error('RPC: protocol error:\n%s' % file.getvalue())
            # Make sure this connection is not reused
            self.connection.aborted = 1

        if self._repo is not None:
            errorLog.notice('RPC: client failed to finish transaction')
            errorLog.info('RPC: attempting rollback')
            try:
                self._repo.txRollback()
            except:
                errorLog.critical('RPC: unable to rollback')
                raise
        return

    # -- command handlers ----------------------------------------------

    _commandMapping = {}

    def _login(self, cmd):
	self.server.errorLog.debug('RPC: login as: %s' % cmd.userName)

        if self._repo is not None:
            raise FtServerServerException(Error.TRANSACTION_IN_PROGRESS)
        self._repo = SCore.GetRepository(cmd.userName, cmd.password,
                                         self.server.errorLog,
                                         self.server.properties)
        return (0, Responses.OkResponse())
    _commandMapping[Commands.CommandCodes.LOGIN] = _login

    def _logout(self,cmd):
        if self._repo is None:
            raise FtServerServerException(Error.TRANSACTION_NOT_IN_PROGRESS)
            
	self.server.errorLog.debug('RPC: logout commit = %s' % str(cmd.commit))
        if cmd.commit:
            self._repo.txCommit()
        else:
            self._repo.txRollback()
        self._repo = None
        return (1, Responses.OkResponse())
    _commandMapping[Commands.CommandCodes.LOGOUT] = _logout

    def _resourceType(self,cmd):
        if self._repo is None:
            raise FtServerServerException(Error.TRANSACTION_NOT_IN_PROGRESS)
        obj = self._repo.fetchResource(cmd.baseObject+';no-traverse')
        obj = obj.fetchResource(cmd.path)

	self.server.errorLog.debug('RPC: resourceType of %s' % str(obj.getDisplayPath()))
	
	rsp = Responses.ResourceTypeResponse(obj.getDisplayPath(),
					     obj.resourceType)
        return (0, rsp)
    _commandMapping[Commands.CommandCodes.RESOURCE_TYPE] = _resourceType

    def _remoteMethod(self,cmd):
        if self._repo is None:
            raise FtServerServerException(Error.TRANSACTION_NOT_IN_PROGRESS)

	self.server.errorLog.debug('RPC: remote method %s on %s' % (cmd.methodName,str(cmd.baseObject)))
        obj = self._repo.fetchResource(cmd.baseObject)
        absPath = obj.getAbsolutePath()
        newArgs = []
        for a in cmd.args:
            if type(a) == type(()):
                a = obj.fetchResource(a[1])
            newArgs.append(a)
        res = getattr(obj, cmd.methodName)(*newArgs)
        if isinstance(res,ResourceMetaDataImp.ResourceMetaDataImp):
            res = res.getAbsolutePath()
        elif type(res) == type([]):
            newRes = []
            for r in res:
                if isinstance(r,ResourceMetaDataImp.ResourceMetaDataImp):
                    newRes.append(r.getAbsolutePath())
                else:
                    newRes.append(r)
            res = newRes
        elif type(res) == type(()):
            newRes = []
            for r in res:
                if isinstance(r,ResourceMetaDataImp.ResourceMetaDataImp):
                    newRes.append(r.getAbsolutePath())
                else:
                    newRes.append(r)
            res = tuple(newRes)
        rsp = Responses.MethodResultsResponse(absPath,res)
        return (0, rsp)
    _commandMapping[Commands.CommandCodes.REMOTE_METHOD] = _remoteMethod


    def _remoteRdf(self,cmd):
        if self._repo is None:
            raise FtServerServerException(Error.TRANSACTION_NOT_IN_PROGRESS)
        obj = self._repo.fetchResource(cmd.baseObject+';no-traverse')
	self.server.errorLog.debug('RPC: remote RDF on %s' % (str(cmd.baseObject)))
        model = obj.getModel()
        res = getattr(model, cmd.methodName)(*cmd.args, **cmd.kw)
        rsp = Responses.RdfResultsResponse(obj.getAbsolutePath(),res)
        return (0, rsp)
    _commandMapping[Commands.CommandCodes.REMOTE_RDF] = _remoteRdf
