# 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()