# 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 """defines the protocol handler interface and a default abstract implementation :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 """ __revision__ = "$Id: ALAbstraction.py,v 1.36 2004/04/02 10:06:46 syt Exp $" __docformat__ = "restructuredtext en" from threading import Thread from narval.public import AL_NS, match_expression from narval.engine_interfaces import IProtocolHandler class ProtocolActivationException(Exception): """Base class for activation exceptions""" class HandlerAlreadyActivated(ProtocolActivationException): """raised if a protocol handler is activated twice for semantically identical activators """ class HandlerNotActivated(ProtocolActivationException): """raised if the handler had not been activated with the element for which it is deactivated, or has not been activated to handle the input, or is not expecting any input """ class ProtocolHandler: """Base class for protocol handlers Derived classes **must** define the following attributes: :type prototype: tuple :cvar prototype: a 3-uple : (('activator desc',('activator matches',)), ('input desc',('input matches',)), ('output desc',('output matches',))) Derived classes **may** override the following attributes: :type namespace: tuple :cvar namespace: a sequence of ('prefix', 'nsuri') used in prototype :type documentation: str or None :cvar documentation: a string documenting the handler. The docstring of the class is used as the documentation if self.documentation is not set. """ __implements__ = IProtocolHandler ACTIVATOR, INPUT, OUTPUT = range(3) DESC, MATCH = range(2) prototype = (('activator desc',('activator matches',)), ('input desc',('input matches',)), ('output desc',('output matches',)) ) namespaces = [('al', AL_NS)] documentation = None def __init__(self, narval): self.narval = narval self.__xml_prototype = None def get_prototype(self): """returns an XML string defining the prototype for the handler The string is a valid XML document, according to the protocol handler's prototype DTD :rtype: str :return: the XML snippet defining the protocol handler's prototype This method uses self.namespaces, self.prototype and self.documentation to generate an XML prototype. """ return self.__build_prototype() def __build_prototype(self): """return the xml prototype. The prototype is built on the first call using instance attributes *namespaces*, *prototype* and *documentation* :rtype: str :return: the XML prototype for the protocol handler """ if self.__xml_prototype is None: proto = self.prototype match = self.MATCH activator = ''.join(["%s" % expr for expr in proto[self.ACTIVATOR][match]]) input = ''.join(["%s" % expr for expr in proto[self.INPUT][match]]) output = ''.join(["%s" % expr for expr in proto[self.OUTPUT][match]]) xmlns = ' '.join(["xmlns:%s=%s" % (p, repr(u)) for (p, u) in self.namespaces]) self.__xml_prototype = ''.join([ "", "", self.documentation or self.__doc__, "", "", "", proto[self.ACTIVATOR][self.DESC], "", activator, "", "" "", proto[self.INPUT][self.DESC], "", input, "", "" "", proto[self.OUTPUT][self.DESC], "", output, "", ""]) return self.__xml_prototype 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 """ 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 """ 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 """ def stop(self): """stops the handler: stops all threads launched by the handler, if any """ def register(self, registry): """register specific stuff for this protocol handler :type registry: `narval.reader.Registry` :param registry: the interpreter's registry """ # utilities ############################################################### def check_output(self, element) : """check that the given element matches all the outputs defined in the prototype :type element: `narval.public.ALElement` :param element: the (usually outgoing) element to check :rtype: bool :return: true if the element satisfy the output prototype """ return self._check_element(self.prototype[self.OUTPUT][self.MATCH], element) def check_input(self, element): """check that element matches one of the inputs defined in the prototype :type element: `narval.public.ALElement` :param element: the (usually incoming) element to check :rtype: bool :return: true if the element satisfy the input prototype """ context = {'elmt': element} for match in self.prototype[self.INPUT][self.MATCH] : if match_expression(match, context): return True return False def check_activator(self, element): """check if element matches the activator :type element: `narval.public.ALElement` :param element: the element to check :rtype: `narval.public.ALElement` or None :return: the element if it satisfies the activator prototype, or None """ if self._check_element(self.prototype[self.ACTIVATOR][self.MATCH], element): return element return None def _check_element(self, prototype, element) : """check if the element match all matches of the prototype :type prototype: list :param prototype: list of match expression to satisfy :type element: `narval.public.ALElement` :param element: the element to check :rtype: bool :return: True if the element satisfy all matches in the prototype """ context = {'elmt': element} for match in prototype: if not match_expression(match, context): return False return True def instantiate_handler_recipe(self, recipe_name, element, context_elements=None): """checks the element against the output declared in the handler's prototype, and instantiates a plan from the recipe with the element in the plan's memory :type recipe_name: str :param recipe_name: the name of the recipe to instantiate (.) :type element: `narval.public.ALElement` :param element: the element generated by the protocol handler :type context_elements: iterable :param element: additional elements to put in the instantiated plan's context """ self.narval.instantiate_handler_recipe(recipe_name, element, self, context_elements) class TwistedProtocolHandler(ProtocolHandler): """a twisted based protocol handler""" REACTOR = None REACTOR_THREAD = None def twisted_run(cls, host, port, factory): # FIXME: handle only TCP """a class method registering twisted protocol handler to the reactor """ reactor = TwistedProtocolHandler.REACTOR need_run = False if reactor is None: from twisted.internet import reactor TwistedProtocolHandler.REACTOR = reactor need_run = True reactor.connectTCP(host, port, factory) if need_run: thread = Thread(target=reactor.run, name='twisted main loop', kwargs={'installSignalHandlers': False}) # FIXME: added this because the twisted thread never return, # even after a call to stop()... thread.setDaemon(True) TwistedProtocolHandler.REACTOR_THREAD = thread thread.start() twisted_run = classmethod(twisted_run) # FIXME: deactivate # FIXME: how to stop/deactivate one single protocol handler ? def stop(self): reactor = TwistedProtocolHandler.REACTOR if reactor is not None: reactor.disconnectAll() #reactor.running = False reactor.callFromThread(reactor.stop)#stop() TwistedProtocolHandler.REACTOR = None #TwistedProtocolHandler.REACTOR_THREAD.join()