# Copyright (c) 2004-2005 DoCoMo Euro-Labs GmbH (Munich, Germany). # Copyright (c) 2001-2005 LOGILAB S.A. (Paris, FRANCE). # # http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com # http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """JabberProtocolHandler acts like a gateway between narval and the jabber server (jabberd). It implements : - class `JabberProtocolHandler`, that contains the methods called by Narval to de/activate the handler and send the data narval generated in the format expected by jabberd - class `JabberService`, which are Jabber clients able to connect to a jabber server with a user specified in the activator element TODO: - roster handling (and more generally iq handling) - auto registration - deactivation / stop a single twisted based protocol handler :version: $Revision:$ :author: Logilab :copyright: 2001-2005 LOGILAB S.A. (Paris, FRANCE) 2004-2005 DoCoMo Euro-Labs GmbH (Munich, Germany) :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com :type JABBER_CLIENT_NS: str :var JABBER_CLIENT_NS: the jabber request qualified name """ __revision__ = "$Id:$" __docformat__ = "restructuredtext en" import sys from twisted.protocols import xmlstream from twisted.protocols.jabber import client, jid from twisted.xish import domish from narval import NO_NS from narval.protocol_handlers import TwistedProtocolHandler, \ HandlerNotActivated, HandlerAlreadyActivated from narval.serialutils import yn_value, yn_rev_value from narval.element import ALElement, NSAttribute from narval.xml_handlers import BaseXMLHandler from narval.interfaces.base import IBaseIMessage, IIMessage, IIPresence from narval.elements.chat import get_discussion JABBER_CLIENT_NS = 'jabber:client' class NotInForumException(Exception): """raised by functions / methods expecting to be called from within a group chat while we are not... """ def get_user(jabber_id, mtype): """return the user id extracted from the jabber id, handling group chats """ if mtype == 'groupchat': return jabber_id.split('/')[-1] return jabber_id.split('@', 1)[0] def get_user_host(jabber_id, mtype): """return the user id extracted from the jabber id, including @host, handling group chats """ if mtype == 'groupchat': # jabber_id has the form conference_room@conference.host/user_id user = jabber_id.split('/', 1)[1] host = jabber_id.split('@', 1)[1].split('/')[0] return '%s@%s' % (user, host.replace('conference.', '')) # jabber_id has the form user_id@host/resource return jabber_id.split('/', 1)[0] def get_server(jabber_id): """return the server part of the jabber id""" return jabber_id.split('/')[0].split('@', 1)[1] def jabber_presence(type=None, to=None, from_=None): """create and return a jabber presence element (ie not wrapped by the narval element) """ presence = domish.Element((JABBER_CLIENT_NS, 'presence')) if type is not None: presence['type'] = type if to is not None: presence['to'] = to if from_ is not None: presence['from'] = from_ return presence def outgoing_message(msgtext, mto, mtype='chat', mfrom=None): """create and return a narval outgoing message element""" msg = JabberRequestElement() msg.type = 'outgoing' jmsg = domish.Element((JABBER_CLIENT_NS, 'message')) jmsg['type'] = mtype jmsg['to'] = mto if mfrom is not None: jmsg['from'] = mfrom msg.request = jmsg msg.set_body(msgtext) return msg def outgoing_presence(ptype=None, pto=None): """create and return a narval outgoing presence element""" msg = JabberPresenceElement() msg.type = 'outgoing' presence = jabber_presence(to=pto, type=ptype)#, from_=self.user) msg.request = presence return msg def _ensure_server(jabber_id, host): """ensure the server is in the jid, add it if necessary""" if jabber_id and not '@' in jabber_id: jabber_id = '%s@%s' % (jabber_id, host) return jabber_id class JabberElementHandler(BaseXMLHandler): """xml handler to read narval message / presence elements""" def __init__(self, elmt, context, locator): BaseXMLHandler.__init__(self, elmt, context, locator) self._body = None def start_element(self, (xmlns, name), attrs): if name in ['presence', 'message']: self.elmt.request = domish.Element((JABBER_CLIENT_NS, name)) self.elmt.set_type(attrs.get( (NO_NS, 'type') )) self.elmt.set_to(attrs.get( (NO_NS, 'to') )) self.elmt.set_from(attrs.get( (NO_NS, 'from') )) elif name == 'body': self._body = domish.Element((JABBER_CLIENT_NS, 'body')) elif name == 'x': # FIXME - for the moment ignored pass # elif name == 'item': # # FIXME - for the moment ignored # self.elmt = None def end_element(self, (xmlns, name)): """SAX callback: close a xml node :type name: tuple :param name: the tag name as a tuple (uri, name) """ if name == 'body': self.elmt.request.addChild(self._body) self._body = None return self.elmt def characters(self, content): if self._body is not None: self._body.addContent(content) class JabberElement(ALElement): """base class for jabber elements, based on twisted jabber support this class is abstract and should not be instatiated """ type = NSAttribute(NO_NS, '', str, str) # incoming / outgoing activator_eid = NSAttribute(NO_NS, None, int, str) __child_handler__ = JabberElementHandler def __init__(self, request=None): ALElement.__init__(self) self.request = request def children_as_xml(self, encoding='UTF-8'): """return the XML representation of the wrapped request element :type encoding: str :param encoding: the encoding to use in the returned string :rtype: str :return: XML string representing the element """ return self.request.toXml() def get_type(self): """return the message's type""" try: return self.request['type'] except KeyError: return 'normal' def is_from_groupchat(self): """ :rtype: bool :return: true if the presence is coming from a group chat and not from a real user """ return self.request['type'] == 'groupchat' def set_type(self, type): """return the message's type""" self.request['type'] = type def get_server(self, conf=None): """return the server used by the given message :type conf: bool :param conf: conference server handling: * if conf is None, the server is returned as it is in the jabber id * if conf is false and the server is the conference server, remove the conference prefix * if conf is true and the server isn't the conference server, append the conference prefix """ server = get_server(self.request['from']) if conf: if not server.startswith('conference.'): server = 'conference.%s' % server elif conf is not None: if server.startswith('conference.'): server = server[11:] return server def get_from(self): """return the sender of the given message""" return self.request['from'].split('@', 1)[0] def set_from(self, from_value): """set the user id of the sender of the given message""" self.request['from'] = from_value def get_to(self): """return the user id of the receiver of the given message, handling group chats """ return self.request['to'].split('@', 1)[0] def set_to(self, to): """set the user id of the receiver of the given message""" self.request['to'] = to def get_from_user(self): """return the user id of the sender of the message, handling group chats""" return get_user(self.request['from'], self.request['type']) def get_from_user_host(self): """return the user id of the sender of the message, including host, handling group chats """ return get_user_host(self.request['from'], self.request['type']) def get_room(self): """return the forum id of the sender of the given message, handling group chats """ if self.get_type() != 'groupchat': raise NotInForumException() if self.type == 'incoming': return self.request['from'].split('/', 1)[0] return self.request['to'].split('/', 1)[0] def get_to_user(self): """return the receiver of the given message""" return get_user(self.request['to'], self.request['type']) def get_to_user_host(self): """return the receiver of the given message, including host""" return get_user_host(self.request['to'], self.request['type']) def build_message(self, body, mto): """create a reply to an incoming request :rtype: `JabberRequestElement` :return: a new message ready to be sent """ msg = outgoing_message(body, self._ensure_server(mto)) msg.activator_eid = self.activator_eid return msg def build_reply(self, answer): """create a reply to an incoming request :rtype: `JabberRequestElement` :return: a new message ready to be sent """ orig_jmsg = self.request mtype = orig_jmsg['type'] # answer to a presence if mtype not in ('chat','groupchat'): mtype = 'chat' # remove resource from the "to" field msg = outgoing_message(answer, orig_jmsg['from'].split('/')[0], mfrom=orig_jmsg['to'], mtype=mtype) msg.activator_eid = self.activator_eid return msg def build_invitation(self, room, guest): """create a set of messages inviting guests to the given conference room """ text = 'You are invited to %s by %s' % (room, self.get_to_user()) invitation = outgoing_message(text, self._ensure_server(guest), mtype='normal') invitation.activator_eid = self.activator_eid x = domish.Element((JABBER_CLIENT_NS, 'x')) x['jid'] = room x['xmlns'] = 'jabber:x:conference' invitation.request.addChild(x) return invitation def build_presence(self, ptype=None, pto=None): presence = outgoing_presence(ptype=ptype, pto=self._ensure_server(pto)) presence.activator_eid = self.activator_eid return presence def _ensure_server(self, jabber_id): """ensure the server is in the jid, add it if necessary""" return _ensure_server(jabber_id, self.get_server()) class JabberPresenceElement(JabberElement): """a memory element wrapping an incoming / outgoing jabber presence """ __implements__ = (IIPresence,) __xml_element__ = (NO_NS, 'jabber-presence') def is_from_groupchat(self): """ :rtype: bool :return: true if the presence is coming from a group chat and not from a real user """ return 'conference' in self.get_server() def get_status(self): """return the presence'status""" # don't use str() since it will additionaly try to encode # the unicode string returned by twisted.xish.domish.Element.__str__ # # i know i know... return self.request.status and self.request.status.__str__() or '' def set_status(self, status): """set the presence'status""" # FIXME: remove existant status if any status_elmt = domish.Element((JABBER_CLIENT_NS, 'status')) status_elmt.addContent(status) self.request.addChild(status_elmt) def get_show(self): """return the presence'show""" # don't use str() since it will additionaly try to encode # the unicode string returned by twisted.xish.domish.Element.__str__ # # i know i know... return self.request.show and self.request.show.__str__() or '' def set_show(self, show): """set the presence'show""" # FIXME: remove existant show if any show_elmt = domish.Element((JABBER_CLIENT_NS, 'show')) show_elmt.addContent(show) self.request.addChild(show_elmt) def children_as_xml(self, encoding='UTF-8'): """return the XML representation of the wrapped request element :type encoding: str :param encoding: the encoding to use in the returned string :rtype: str :return: XML string representing the element """ return self.request.toXml() class JabberRequestElement(JabberElement): """a memory element wrapping an incoming / outgoing jabber message :type request: :ivar request: the original request object :type type: str :ivar type: the type of the request, i.e. 'incoming' or 'outgoing' :type activator_eid: int :ivar activator_eid: the eid of the jabber service activator """ __implements__ = (IIMessage,) __xml_element__ = (NO_NS, 'jabber-request') def master_id(self, master_info): """return the master id for this protocol, taken from information available in the given MasterInformationElement """ return master_info.jabberid def get_body(self): """return the content of the message""" # don't use str() since it will additionaly try to encode # the unicode string returned by twisted.xish.domish.Element.__str__ # # i know i know... return self.request.body.__str__().strip() or '' def set_body(self, text): if self.request.body: self.request.children.remove(self.request.body) body = domish.Element((JABBER_CLIENT_NS, 'body')) body.addChild(text) self.request.addChild(body) class JabberActivatorElement(ALElement): """a memory element used to activate a jabber handler :type recipe: str :ivar recipe: the name of the recipe (.) used to handle queries :type user: str :ivar user: the jabber user's name :type password: str :ivar password: the jabber user's password :type resource: str :ivar resource: the jabber resource :type host: str :ivar host: the jabber server host name, default to localhost :type port: int :ivar port: the jabber server port, default to 5222 :type register: bool :ivar register: indicates whether we should try to auto-register to the server if necessary """ __xml_element__ = (NO_NS, 'jabber-activator') recipe = NSAttribute(NO_NS, None, str, str) host = NSAttribute(NO_NS, 'localhost', str, str) port = NSAttribute(NO_NS, 5222, int, str) user = NSAttribute(NO_NS, None, str, str) password = NSAttribute(NO_NS, None, str, str) resource = NSAttribute(NO_NS, 'narvabber', str, str) register = NSAttribute(NO_NS, False, yn_value, yn_rev_value) verbose = NSAttribute(NO_NS, False, yn_value, yn_rev_value) def master_id(self, master_info): """return the master id for this protocol, taken from information available in the given MasterInformationElement """ return master_info.jabberid def create_msg(self, msgtext, mto, mtype='chat'): """create a raw message :rtype: `JabberRequestElement` :return: a new message ready to be sent """ msg = outgoing_message(msgtext, self._ensure_server(mto), mtype=mtype) msg.activator_eid = self.eid return msg def create_presence(self, pto, ptype='available'): """create a raw presence :rtype: `JabberRequestElement` :return: a new message ready to be sent """ msg = outgoing_presence(ptype, self._ensure_server(pto)) msg.activator_eid = self.eid return msg def _ensure_server(self, jabber_id): """ensure the server is in the jid, add it if necessary""" return _ensure_server(jabber_id, self.host) class JabberProtocolHandler(TwistedProtocolHandler): """a Jabber client delegating non jabber specific operations to the narval interpreter :type narval: `narval.engine.Narval` :ivar narval: a reference to the narval interpreter """ namespaces = TwistedProtocolHandler.namespaces + [('jabber', JABBER_CLIENT_NS)] def __init__(self, narval): TwistedProtocolHandler.__init__(self, narval) # this prototype is not complete. # will see the spec. in Jabber doc, to inform the non-facultative # elements self.prototype = ( ('port on which the server listens, recipe to instanciate', ('isinstance(elmt, JabberActivatorElement) and elmt.recipe \ and elmt.user and elmt.password',) ), ('response to a Jabber request', ('IBaseIMessage(elmt).type == "outgoing"',) # # FIXME (syt) : removed "and isinstance(IBaseIMessage(elmt), JabberElement)" # since it doesn't work with adapters. Will have to add a test doing the same kind of verification # when we'll have omre than one implementation of IBaseIMessage and co ), ('Jabber request', ('IBaseIMessage(elmt).type == "incoming" and isinstance(IBaseIMessage(elmt), JabberElement)',) ) ) self.eid_activators = {} self.activations = {} def activate(self, activator): """activates the handler with the given element. A handler can be activated more than once, and should behave accordingly. For instance an HTTP handler can be activated on different ports. The handler should use the eid of the element to be able to deactivate the proper service. :type activator: `narval.public.ALElement` :param activator: the element activating the handler :raise HandlerAlreadyActivated: if the activate method already has been called with a semantically identical activator """ host = activator.host port = activator.port user = activator.user eid = activator.eid key = (user, host, port) if self.activations.has_key(key): raise HandlerAlreadyActivated(key) self.activations[key] = eid log(LOG_INFO, "Activating jabber protocol handler for %s@%s:%s", key) recipe = activator.recipe password = activator.password resource = activator.resource jabber_id = "%s@%s/%s" % (user, host, resource) myjid = jid.JID(jabber_id) factory = client.basicClientFactory(myjid, password) service = JabberService(activator, self, factory, recipe, jabber_id) self.eid_activators[eid] = service self.twisted_run(host, port, factory) log(LOG_INFO, "Connected to jabber server %s", host) def deactivate(self, eid): """deactivates the handler. :type eid: int :param eid: the eid of the element deactivating the handler :raise HandlerNotActivated: if the handler had not been activated with the element """ try: service = self.eid_activators[eid] except KeyError: raise HandlerNotActivated(eid) # FIXME self.stop() ## service = self.activations[port][0] ## service.running = None ## def stop(self): ## """stops the handler : stops all threads launched by the handler, if any ## """ ## for service, thread in self.activations.values(): ## service.running = None ## thread.join() def handle_outgoing(self, element): """passes an element matching one of the inputs of the handler. :raise `HandlerNotActivated`: if the handler has not been activated, or has not been activated to handle the input, or is not expecting any input. :type element: `narval.public.ALElement` :param element: the outgoing element """ # need to adapt here, the element may only be an adaptable element, # not a IBaseIMessage itself element = IBaseIMessage(element) try: service = self.eid_activators[element.activator_eid] # FIXME: service may not yet have the 'send' method if not yet authenticated ! service.send_element(element) except: log_traceback(LOG_ERR, sys.exc_info()) class JabberService: """a jabber service handles a connection for a given user / server :type jabber_id: str :ivar jabber_id: the jabber identifier of the user used by the handler :type protocol_handler: `JabberProtocolHandler` :ivar protocol_handler: the protocol handler which has instantiated this service :type recipe: str :ivar recipe: the name of the recipe to handle incoming messages :type server_addr: tuple :ivar server_addr: the jabber server address as a tuple (host, port) :type running: bool :ivar running: indicates whether the service is currently running """ def __init__(self, activator, protocol_handler, factory, recipe, jabber_id): """Initialize the connection to a JabberServer :type server_addr: tuple :param server_addr: the jabber server address as a tuple (host, port) :type protocol_handler: `JabberProtocolHandler` :param protocol_handler: the protocol handler which has instantiated this service :type recipe: str :param recipe: the name of the recipe to handle incoming messages :type jabber_id: str :param jabber_id: the jabber identifier of the user used by the handler """ self.activator = activator self.protocol_handler = protocol_handler self.factory = factory self.recipe = recipe self.jabber_id = jabber_id self.nickname = jabber_id.split('@', 1)[0] self.waiting_roster = False self.contexts = {} factory.addBootstrap(xmlstream.STREAM_AUTHD_EVENT, self.authenticate) factory.addBootstrap(client.BasicAuthenticator.INVALID_USER_EVENT, self.invalid_user) factory.addBootstrap(client.BasicAuthenticator.AUTH_FAILED_EVENT, self.fatal_error) factory.addBootstrap(client.BasicAuthenticator.REGISTER_FAILED_EVENT, self.fatal_error) def process_element(self, elmt): """message processing method : build a narval element from the request and give it to the narval interpreter as context of a newly created plan :type elmt: `JabberRequestElement` or `JabberPresenceElement` :param elmt: the jabber request wrapped by it's corresponding narval element """ elmt.type = 'incoming' elmt.activator_eid = self.activator.eid self._set_context(elmt) try: self.protocol_handler.instantiate_handler_recipe(self.recipe, elmt, [self.activator]) except: log_traceback(LOG_ERR, sys.exc_info()) # FIXME: put a more significant xml error, of jabberPacket type!!! # FIXME: not sure the line below work with twisted... self._send("") raise def send_element(self, elmt): if not hasattr(self, '_send'): raise Exception('not yet ready to send messages !') log(LOG_DEBUG, 'sending msg : %s', elmt.request.toXml()) self._set_context(elmt) self._send(elmt.request) def _set_context(self, elmt): """set the thread context on the element :type elmt: `JabberRequestElement` or `JabberPresenceElement` :param elmt: the jabber request wrapped by it's corresponding narval element """ if elmt.get_type() == 'groupchat': context_key = elmt.get_room() elif elmt.type == 'incoming': context_key = elmt.get_from() else: context_key = elmt.get_to() discussion = get_discussion(self.activator, context_key) discussion.append(elmt) elmt.in_discussion = discussion elmt.discussion_mode = discussion.mode # callbacks methods ####################################################### def authenticate(self, twxml): """authentication callback""" # bind twxml.send to self self._send = twxml.send # add observer for incoming message / presence requests twxml.addObserver("/iq", self.iq_handler) twxml.addObserver("/presence", self.presence_handler) twxml.addObserver("/message", self.message_handler) #twxml.addObserver('/*', self.something_handler) # request roster self.waiting_roster = True log(LOG_DEBUG, 'requesting roster') iq = domish.Element((JABBER_CLIENT_NS, 'iq')) iq['type'] = 'get' iq.addElement(('jabber:iq:roster', 'query')) self._send(iq) def invalid_user(self, twxml): """invalid user callback""" log(LOG_NOTICE, 'invalid jabber user (%s)', twxml) ctrl = self.activator # should we try to register a new account to the server ? if ctrl.register: user = ctrl.user log(LOG_NOTICE, 'registering user %s to the jabber server', user) self.factory.authenticator.registerAccount(user, ctrl.password) else: self.fatal_error(twxml) def fatal_error(self, twxml): """unrecoverable error callback""" log(LOG_ERR, 'unrecoverable error: %s', twxml.toXml()) log(LOG_ERR, 'stopping service !') self.protocol_handler.deactivate(self.activator.eid) def something_handler(self, twxml): """handle unknown packets :type twxml: `twisted.xish.domish.Element` :param twxml: the xml stream containing the element """ if self.activator.verbose: print '------------------------------------- got what ???' print twxml.toXml() def iq_handler(self, twxml): """handle a iq packet :type twxml: `twisted.xish.domish.Element` :param twxml: the xml stream containing a iq element """ if self.activator.verbose: print '------------------------------------- got iq' print twxml.toXml() if self.waiting_roster and twxml.query.uri == 'jabber:iq:roster': # got roster, send presence so clients know we're actually online log(LOG_DEBUG, 'sending presence') presence = domish.Element((JABBER_CLIENT_NS, 'presence')) presence.addElement('status').addContent('Online') self._send(presence) self.waiting_roster = False def presence_handler(self, presence): """handle a presence packet :type presence: `twisted.xish.domish.Element` :param presence: the xml stream containing a presence element """ if self.activator.verbose: print '------------------------------------- got presence' print presence.toXml() # FIXME: do this in a recipe ? try: ptype = presence['type'] except KeyError: presence['type'] = ptype = 'available' who = presence['from'].split('/')[0] if ptype == 'subscribe': # FIXME: subscription policy # subscription request: # accept subscription and send request for subscription to # their presence log(LOG_INFO, 'subscribe request from %s', who) self._send(jabber_presence(to=who, type='subscribed', from_=self.jabber_id)) self._send(jabber_presence(to=who, type='subscribe', from_=self.jabber_id)) elif ptype == 'unsubscribe': # unsubscription request: # accept unsubscription and send request for unsubscription to # their presence log(LOG_INFO, 'unsubscribe request from %s', who) self._send(jabber_presence(to=who, type='unsubscribed', from_=self.jabber_id)) self._send(jabber_presence(to=who, type='unsubscribe', from_=self.jabber_id)) elif ptype == 'subscribed': log(LOG_INFO, 'we are now subscribred from %s', who) elif ptype == 'unsubscribed': log(LOG_INFO, 'we are now unsubscribred from %s', who) elif ptype == 'available': show = presence.getAttribute('show') or '' status = presence.getAttribute('status') or '' log(LOG_INFO, '%s is available (%s/%s)', (who, show, status)) elif ptype == 'unavailable': log(LOG_INFO, '%s is unavailable', who) else: log(LOG_NOTICE, 'unknown presence type %r from %s', (ptype, who)) self.process_element(JabberPresenceElement(presence)) def message_handler(self, twxml): """handle a message packet :type twxml: `twisted.xish.domish.Element` :param twxml: the xml stream containing a message element """ if self.activator.verbose: print '------------------------------------- got message' print twxml.toXml() for elmt in twxml.elements(): # invited in a group chat ? # in this case, just send a presence element to accept invitation # and do not further processing for this message if elmt.uri == 'jabber:x:conference': nick = self.jabber_id.split('@')[0] presence = jabber_presence(to='%s/%s' % (elmt['jid'], nick)) self._send(presence) break # ignore delayed / error messages if elmt.uri == 'jabber:x:delay' or elmt.getAttribute('type') == 'error': break else: from_user = get_user(twxml['from'], twxml.getAttribute('type')) # ignore message from myself, or from group chat (ie control # messages sent with the chat room as user, and in this case # from_user is room@server) if from_user != self.nickname and not '@' in from_user: self.process_element(JabberRequestElement(twxml)) def register(engine): """register the protocol handler and associated elements to the engine :type engine: `narval.engine.Narval` :param registry: the narval interpreter """ ## if engine.test_mode: ## engine.register_protocol_handler('jabber', TestJabberProtocolHandler) ## TestJabberActivatorElement.__name__ = 'JabberActivatorElement' ## engine.registry.register_class(TestJabberActivatorElement) ## else: engine.register_protocol_handler('jabber', JabberProtocolHandler) engine.registry.register_class(JabberActivatorElement) engine.registry.register_class(JabberRequestElement) engine.registry.register_class(JabberPresenceElement) # maybe usefull to use in expression testing for message or presence engine.registry.register_class(JabberElement) ## # functional test jph ######################################################### ## from threading import Thread, Lock ## import time ## from narval.xml_handlers import ListHandler ## class TestJabberActivatorElement(JabberActivatorElement): ## __xml_element__ = (NO_NS, 'jabber-activator') ## __child_handler__ = ListHandler ## list_attr = 'discussion' ## discussion = () ## def children_as_xml(self, encoding='UTF-8'): ## """return the XML representation of children in this element. ## :type encoding: str ## :param encoding: the encoding to use in the returned string ## :rtype: str ## :return: XML string representing the element ## """ ## result = [] ## for value in self.discussion: ## result.append(' %s' % (value,)) ## return '\n'.join(result) ## class TestJabberProtocolHandler(ProtocolHandler): ## """a Jabber client delegating non jabber specific operations to the narval ## interpreter, used to test narval's interaction with the jabber protocol ## handler ## :type narval: `narval.engine.Narval` ## :ivar narval: a reference to the narval interpreter ## """ ## namespaces = TwistedProtocolHandler.namespaces + [('jabber', JABBER_CLIENT_NS)] ## def __init__(self, narval): ## self.prototype = ( ## ('port on which the server listens, recipe to instanciate', ## ('isinstance(elmt, JabberActivatorElement) and elmt.recipe',) ## ), ## ('response to a Jabber request', ## ('IBaseIMessage(elmt).type == "outgoing"',) ## # ## # FIXME (syt) : removed "and isinstance(IBaseIMessage(elmt), JabberElement)" ## # since it doesn't work with adapters. Will have to add a test doing the same kind of verification ## # when we'll have omre than one implementation of IBaseIMessage and co ## ), ## ('Jabber request', ## ('IBaseIMessage(elmt).type == "incoming" and isinstance(IBaseIMessage(elmt), JabberElement)',) ## ) ## ) ## self.narval = narval ## def activate(self, activator): ## """activates the handler with the given element. ## A handler can be activated more than once, and should behave ## accordingly. For instance an HTTP handler can be activated on ## different ports. The handler should use the eid of the element ## to be able to deactivate the proper service. ## :type activator: `narval.public.ALElement` ## :param activator: the element activating the handler ## :raise HandlerAlreadyActivated: ## if the activate method already has been called with a semantically ## identical activator ## """ ## log(LOG_INFO, "Activating test jabber protocol handler") ## self.discussion_lock = Lock() ## self.error = False ## Thread(target=self.simulate_discussion, args=(activator,)).start() ## def deactivate(self, eid): ## pass ## def simulate_discussion(self, activator): ## # begin with a litle sleep to avoid starting test until initialization's end ## time.sleep(3) ## lock = self.discussion_lock ## #print 'ACQUIRING' ## lock.acquire() ## try: ## while activator.discussion and not self.error: ## # get the theorically incoming message ## input_sentence = activator.discussion.pop(0) ## # set expected answer ## try: ## self.currently_expected = activator.discussion.pop(0) ## except IndexError: ## break ## # create and send the incoming message as a JabberMessageElement ## msg = activator.create_msg(input_sentence, 'narval@toto.com') ## msg.set_from('user@toto.com') ## msg.type = 'incoming' ## self.instantiate_handler_recipe(activator.recipe, msg, [activator]) ## # wait for the reply if necessary (FIXME: timeout -> use a Condition) ## #print 'ACQUIRING, BLOCKING' ## lock.acquire() ## finally: ## #print 'RELEASING, FINALLY' ## lock.release() ## self.narval.quit() ## def handle_outgoing(self, element): ## try: ## # check answer, generate a comprehensive error if unexpected ## # need to normalize string because the list handler may slightly change ## # the expected message (duh?)... ## try: ## assert normstr(element.get_body()) == normstr(self.currently_expected), \ ## '%r != %r' % (normstr(element.get_body()), ## normstr(self.currently_expected)) ## except: ## self.error = True ## raise ## finally: ## #print 'RELEASING' ## self.discussion_lock.release() ## def normstr(string): ## return ' '.join(string.split())