########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Server/Server/Xslt/XmlRpcClient.py,v 1.5 2005/04/06 23:05:46 jkloth Exp $
"""
XSLT extensions allowing limited access to external XML-RPC servers

Copyright 2004 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

import xmlrpclib
from Ft.Xml.XPath import Util
from Ft.Xml.XPath import Conversions
from Ft.Xml.Xslt import XsltElement
from Ft.Xml.Xslt import ContentInfo, AttributeInfo
from Ns import UTIL_NS


CONVERTERS_FROM_XSLT = {
    None: None,
    "INT": int,
    "DOUBLE": float,
    "BOOLEAN": lambda x: xmlrpclib.Boolean(not x and 1 or 0),
    "STRING": str,
    }


class XmlRpcInvokeElement(XsltElement):
    """
    Invoke an XML-RPC server at a remote URI (Requires Python 2.2 or above)
    There are 5 attributes, p1, p2... which are used for the positional
    arguments to the remote method [this will be made less kludgy soon]
    Each one is an expression which follows the following conversion rules
    from the XPath data model:

        string -> string
        number -> floating-point number
        boolean -> boolean
        node set -> array of strings

    You can also coerce the value to a certain type by using an attribute
    of the form t1, t2,... corresponding to the pN attribute with one of the
    following string values:

    INT
    DOUBLE
    STRING
    BOOLEAN

    The return value is converted into an XML fragment representation,
    which is sent to output.

        string -> <String>[value]</String>
        floating-point number -> <Double>[value]</Double>
        boolean -> boolean <Boolean>["true" or "false"]</Boolean>
        array -> <Array>[sequence of child elements as above, according to array elements]</Array>

    An example of an array:

        <Array>
          <String>a string</String>
          <Float>123.4</Float>
          <Int>1234</Int>
          <String>another string</String>
        </Array>

    This means that in order to access a remote method

    obj.spam('eggs', 3)

    On a remote server at

    http://spam.com/xmlrpcserver

    And save the result of XSLT variable "monty",
    you can use the following XSLT snippet:

    <xsl:variable name="monty">
      <futil:xml-rpc-invoke uri="http://spam.com/xmlrpcserver"
                            method="obj.spam"
                            p1="'eggs'"
                            p2="3" t2="INT"/>
    </xsl:variable>

    No type coercion is used for p1 because XSLT string are generally
    unambiguous in converting to XML-RPC.  However, the second param
    could be integer or double, so it is coerced (default is double).

    Beware that if an array is returned, you might need to use
    the exslt:node-set function to convert from result tree fragment
    to node set.
    """

    content = ContentInfo.Empty

    legalAttrs = {
        'uri' : AttributeInfo.UriReferenceAvt(
            required=1,
            description='The URI of the remote server'),
        'method' : AttributeInfo.StringAvt(
            required=1,
            description='The method name to be invoked'),
        'p1' : AttributeInfo.Expression(
            description='The first parameter for the remote server'),
        't1' : AttributeInfo.Expression(
            description='Optional type spec for the first parameter'),
        'p2' : AttributeInfo.Expression(
            description='The second parameter for the remote server'),
        't2' : AttributeInfo.Expression(
            description='Optional type spec for the The second parameter'),
        'p3' : AttributeInfo.Expression(
            description='The third parameter for the remote server'),
        't3' : AttributeInfo.Expression(
            description='Optional type spec for the The third parameter'),
        'p4' : AttributeInfo.Expression(
            description='The fourth parameter for the remote server'),
        't4' : AttributeInfo.Expression(
            description='Optional type spec for the The fourth parameter'),
        'p5' : AttributeInfo.Expression(
            description='The fifth parameter for the remote server'),
        't5' : AttributeInfo.Expression(
            description='Optional type spec for the The fifth parameter'),
        }

    _proxies = {}

    def instantiate(self, context, processor):
        context.setProcessState(self)

        uri = self._uri.evaluate(context)
        method_name = self._method.evaluate(context)

        params = []
        for i in range(1, 5):
            p = eval('self._p'+str(i))
            t = eval('self._t'+str(i))
            if p:
                p = p.evaluate(context)
                conv = CONVERTERS_FROM_XSLT[t and t.evaluate(context)]
                if conv: p = conv(p)
                params.append(p)
        print "Invoking XML-RPC method", method_name, "on server at", uri
        print "Parameters", params

        if self._proxies.has_key(uri):
            proxy = self._proxies[uri]
        else:
            proxy = xmlrpclib.ServerProxy(uri)
            self._proxies[uri] = proxy
        method = getattr(proxy, method_name)
        results = method(*params)
        WriteXmlRpcResults(processor, results)
        return

def WriteXmlRpcResults(processor, results):
    if isinstance(results, list):
        processor.writers[-1].startElement(u'Array')
        for result in results:
            WriteXmlRpcResults(processor, results)
        processor.writers[-1].endElement(u'Array')
    elif isinstance(results, int):
        processor.writers[-1].startElement(u'Int')
        processor.writers[-1].text(str(results))
        processor.writers[-1].endElement(u'Int')
    elif isinstance(results, float):
        processor.writers[-1].startElement(u'Double')
        processor.writers[-1].text(str(results))
        processor.writers[-1].endElement(u'Double')
    elif isinstance(results, str):
        processor.writers[-1].startElement(u'String')
        processor.writers[-1].text(results)
        processor.writers[-1].endElement(u'String')
    elif isinstance(results, xmlrpclib.Boolean):
        processor.writers[-1].startElement(u'Boolean')
        processor.writers[-1].text(results and 'true' or 'false')
        processor.writers[-1].endElement(u'Boolean')
    elif isinstance(results, xmlrpclib.DateTime):
        processor.writers[-1].startElement(u'Date')
        processor.writers[-1].text(results.value)
        processor.writers[-1].endElement(u'Date')
    return


ExtFunctions = {}

ExtElements = {
    (UTIL_NS, 'xml-rpc-invoke'): XmlRpcInvokeElement,
    }

