pyxmpp.jabberd.component

Package pyxmpp :: Package jabberd :: Module component |
[hide private]

# Source Code for Module pyxmpp.jabberd.component

      1  #
      2  # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net>
      3  #
      4  # This program is free software; you can redistribute it and/or modify
      5  # it under the terms of the GNU Lesser General Public License Version
      6  # 2.1 as published by the Free Software Foundation.
      7  #
      8  # This program is distributed in the hope that it will be useful,
      9  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     11  # GNU Lesser General Public License for more details.
     12  #
     13  # You should have received a copy of the GNU Lesser General Public
     14  # License along with this program; if not, write to the Free Software
     15  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     16  #
     17
     18  """Jabberd external component interface (jabber:component:accept).
     19
     20  Normative reference:
     21    - `JEP 114 <http://www.jabber.org/jeps/jep-0114.html>`__
     22  """
     23
     24  __docformat__="restructuredtext en"
     25
     26  import threading
     27  import logging
     28
     29  from pyxmpp.jabberd.componentstream import ComponentStream
     30  from pyxmpp.utils import from_utf8
     31  from pyxmpp.jabber.disco import DiscoItems,DiscoInfo,DiscoIdentity
     32  from pyxmpp.stanza import Stanza
     33
     34 -class Component:
     35      """Jabber external component ("jabber:component:accept" protocol) interface
     36      implementation.
     37
     38      Override this class to build your components.
     39
     40      :Ivariables:
     41          - `jid`: component JID (should contain only the domain part).
     42          - `secret`: the authentication secret.
     43          - `server`: server to which the commonent will connect.
     44          - `port`: port number on the server to which the commonent will
     45            connect.
     46          - `keepalive`: keepalive interval for the stream.
     47          - `stream`: the XMPP stream object for the active connection
     48            or `None` if no connection is active.
     49          - `disco_items`: disco items announced by the component. Created
     50            when a stream is connected.
     51          - `disco_info`: disco info announced by the component. Created
     52            when a stream is connected.
     53          - `disco_identity`: disco identity (part of disco info) announced by
     54            the component. Created when a stream is connected.
     55          - `disco_category`: disco category to be used to create
     56            `disco_identity`.
     57          - `disco_type`: disco type to be used to create `disco_identity`.
     58
     59      :Types:
     60          - `jid`:  `pyxmpp.JID`
     61          - `secret`: `unicode`
     62          - `server`: `unicode`
     63          - `port`: `int`
     64          - `keepalive`: `int`
     65          - `stream`: `pyxmpp.jabberd.ComponentStream`
     66          - `disco_items`: `pyxmpp.jabber.DiscoItems`
     67          - `disco_info`: `pyxmpp.jabber.DiscoInfo`
     68          - `disco_identity`: `pyxmpp.jabber.DiscoIdentity`
     69          - `disco_category`: `str`
     70          - `disco_type`: `str`"""
     71 -    def __init__(self, jid=None, secret=None, server=None, port=5347,
     72              disco_name=u"PyXMPP based component", disco_category=u"x-service",
     73              disco_type=u"x-unknown", keepalive=0):
     74          """Initialize a `Component` object.
     75
     76          :Parameters:
     77              - `jid`: component JID (should contain only the domain part).
     78              - `secret`: the authentication secret.
     79              - `server`: server name or address the component should connect.
     80              - `port`: port number on the server where the component should connect.
     81              - `disco_name`: disco identity name to be used in the
     82                disco#info responses.
     83              - `disco_category`: disco identity category to be used in the
     84                disco#info responses.  Use `the categories registered by Jabber Registrar <http://www.jabber.org/registrar/disco-categories.html>`__
     85              - `disco_type`: disco identity type to be used in the component's
     86                disco#info responses.  Use `the types registered by Jabber Registrar <http://www.jabber.org/registrar/disco-categories.html>`__
     87              - `keepalive`: keepalive interval for the stream.
     88
     89          :Types:
     90              - `jid`:  `pyxmpp.JID`
     91              - `secret`: `unicode`
     92              - `server`: `str` or `unicode`
     93              - `port`: `int`
     94              - `disco_name`: `unicode`
     95              - `disco_category`: `unicode`
     96              - `disco_type`: `unicode`
     97              - `keepalive`: `int`"""
     98          self.jid=jid
     99          self.secret=secret
    100          self.server=server
    101          self.port=port
    102          self.keepalive=keepalive
    103          self.stream=None
    104          self.lock=threading.RLock()
    105          self.state_changed=threading.Condition(self.lock)
    106          self.stream_class=ComponentStream
    107          self.disco_items=DiscoItems()
    108          self.disco_info=DiscoInfo()
    109          self.disco_identity=DiscoIdentity(self.disco_info,
    110                              disco_name, disco_category, disco_type)
    111          self.register_feature("stringprep")
    112          self.__logger=logging.getLogger("pyxmpp.jabberd.Component")
    113
    114  # public methods
    115
    116 -    def connect(self):
    117          """Establish a connection with the server.
    118
    119          Set `self.stream` to the `pyxmpp.jabberd.ComponentStream` when
    120          initial connection succeeds.
    121
    122          :raise ValueError: when some of the component properties
    123            (`self.jid`, `self.secret`,`self.server` or `self.port`) are wrong."""
    124          if not self.jid or self.jid.node or self.jid.resource:
    125              raise ValueError,"Cannot connect: no or bad JID given"
    126          if not self.secret:
    127              raise ValueError,"Cannot connect: no secret given"
    128          if not self.server:
    129              raise ValueError,"Cannot connect: no server given"
    130          if not self.port:
    131              raise ValueError,"Cannot connect: no port given"
    132
    133          self.lock.acquire()
    134          try:
    135              stream=self.stream
    136              self.stream=None
    137              if stream:
    138                  stream.close()
    139
    140              self.__logger.debug("Creating component stream: %r" % (self.stream_class,))
    141              stream=self.stream_class(jid = self.jid,
    142                      secret = self.secret,
    143                      server = self.server,
    144                      port = self.port,
    145                      keepalive = self.keepalive,
    146                      owner = self)
    147              stream.process_stream_error=self.stream_error
    148              self.stream_created(stream)
    149              stream.state_change=self.__stream_state_change
    150              stream.connect()
    151              self.stream=stream
    152              self.state_changed.notify()
    153              self.state_changed.release()
    154          except:
    155              self.stream=None
    156              self.state_changed.release()
    157              raise
    158
    159 -    def get_stream(self):
    160          """Get the stream of the component in a safe way.
    161
    162          :return: Stream object for the component or `None` if no connection is
    163              active.
    164          :returntype: `pyxmpp.jabberd.ComponentStream`"""
    165          self.lock.acquire()
    166          stream=self.stream
    167          self.lock.release()
    168          return stream
    169
    170 -    def disconnect(self):
    171          """Disconnect from the server."""
    172          stream=self.get_stream()
    173          if stream:
    174              stream.disconnect()
    175
    176 -    def socket(self):
    177          """Get the socket of the connection to the server.
    178
    179          :return: the socket.
    180          :returntype: `socket.socket`"""
    181          return self.stream.socket
    182
    183 -    def loop(self,timeout=1):
    184          """Simple 'main loop' for a component.
    185
    186          This usually will be replaced by something more sophisticated. E.g.
    187          handling of other input sources."""
    188          self.stream.loop(timeout)
    189
    190 -    def register_feature(self, feature_name):
    191          """Register a feature to be announced by Service Discovery.
    192
    193          :Parameters:
    194              - `feature_name`: feature namespace or name.
    195          :Types:
    196              - `feature_name`: `unicode`"""
    197          self.disco_info.add_feature(feature_name)
    198
    199 -    def unregister_feature(self, feature_name):
    200          """Unregister a feature to be announced by Service Discovery.
    201
    202          :Parameters:
    203              - `feature_name`: feature namespace or name.
    204          :Types:
    205              - `feature_name`: `unicode`"""
    206          self.disco_info.remove_feature(feature_name)
    207
    208
    209  # private methods
    210 -    def __stream_state_change(self,state,arg):
    211          """Handle various stream state changes and call right
    212          methods of `self`.
    213
    214          :Parameters:
    215              - `state`: state name.
    216              - `arg`: state parameter.
    217          :Types:
    218              - `state`: `string`
    219              - `arg`: any object"""
    220          self.stream_state_changed(state,arg)
    221          if state=="fully connected":
    222              self.connected()
    223          elif state=="authenticated":
    224              self.authenticated()
    225          elif state=="authorized":
    226              self.authorized()
    227          elif state=="disconnected":
    228              self.state_changed.acquire()
    229              try:
    230                  if self.stream:
    231                      self.stream.close()
    232                  self.stream_closed(self.stream)
    233                  self.stream=None
    234                  self.state_changed.notify()
    235              finally:
    236                  self.state_changed.release()
    237              self.disconnected()
    238
    239 -    def __disco_info(self,iq):
    240          """Handle a disco-info query.
    241
    242          :Parameters:
    243              - `iq`: the stanza received.
    244
    245          Types:
    246              - `iq`: `pyxmpp.Iq`"""
    247          q=iq.get_query()
    248          if q.hasProp("node"):
    249              node=from_utf8(q.prop("node"))
    250          else:
    251              node=None
    252          info=self.disco_get_info(node,iq)
    253          if isinstance(info,DiscoInfo):
    254              resp=iq.make_result_response()
    255              self.__logger.debug("Disco-info query: %s preparing response: %s with reply: %s"
    256                  % (iq.serialize(),resp.serialize(),info.xmlnode.serialize()))
    257              resp.set_content(info.xmlnode.copyNode(1))
    258          elif isinstance(info,Stanza):
    259              resp=info
    260          else:
    261              resp=iq.make_error_response("item-not-found")
    262          self.__logger.debug("Disco-info response: %s" % (resp.serialize(),))
    263          self.stream.send(resp)
    264
    265 -    def __disco_items(self,iq):
    266          """Handle a disco-items query.
    267
    268          :Parameters:
    269              - `iq`: the stanza received.
    270
    271          Types:
    272              - `iq`: `pyxmpp.Iq`"""
    273          q=iq.get_query()
    274          if q.hasProp("node"):
    275              node=from_utf8(q.prop("node"))
    276          else:
    277              node=None
    278          items=self.disco_get_items(node,iq)
    279          if isinstance(items,DiscoItems):
    280              resp=iq.make_result_response()
    281              self.__logger.debug("Disco-items query: %s preparing response: %s with reply: %s"
    282                  % (iq.serialize(),resp.serialize(),items.xmlnode.serialize()))
    283              resp.set_content(items.xmlnode.copyNode(1))
    284          elif isinstance(items,Stanza):
    285              resp=items
    286          else:
    287              resp=iq.make_error_response("item-not-found")
    288          self.__logger.debug("Disco-items response: %s" % (resp.serialize(),))
    289          self.stream.send(resp)
    290
    291  # Method to override
    292 -    def idle(self):
    293          """Do some "housekeeping" work like <iq/> result expiration. Should be
    294          called on a regular basis, usually when the component is idle."""
    295          stream=self.get_stream()
    296          if stream:
    297              stream.idle()
    298
    299 -    def stream_created(self,stream):
    300          """Handle stream creation event.
    301
    302          [may be overriden in derived classes]
    303
    304          By default: do nothing.
    305
    306          :Parameters:
    307              - `stream`: the stream just created.
    308          :Types:
    309              - `stream`: `pyxmpp.jabberd.ComponentStream`"""
    310          pass
    311
    312 -    def stream_closed(self,stream):
    313          """Handle stream closure event.
    314
    315          [may be overriden in derived classes]
    316
    317          By default: do nothing.
    318
    319          :Parameters:
    320              - `stream`: the stream just created.
    321          :Types:
    322              - `stream`: `pyxmpp.jabberd.ComponentStream`"""
    323          pass
    324
    325 -    def stream_error(self,err):
    326          """Handle a stream error received.
    327
    328          [may be overriden in derived classes]
    329
    330          By default: just log it. The stream will be closed anyway.
    331
    332          :Parameters:
    333              - `err`: the error element received.
    334          :Types:
    335              - `err`: `pyxmpp.error.StreamErrorNode`"""
    336          self.__logger.debug("Stream error: condition: %s %r"
    337                  % (err.get_condition().name,err.serialize()))
    338
    339 -    def stream_state_changed(self,state,arg):
    340          """Handle a stream state change.
    341
    342          [may be overriden in derived classes]
    343
    344          By default: do nothing.
    345
    346          :Parameters:
    347              - `state`: state name.
    348              - `arg`: state parameter.
    349          :Types:
    350              - `state`: `string`
    351              - `arg`: any object"""
    352          pass
    353
    354 -    def connected(self):
    355          """Handle stream connection event.
    356
    357          [may be overriden in derived classes]
    358
    359          By default: do nothing."""
    360          pass
    361
    362 -    def authenticated(self):
    363          """Handle successful authentication event.
    364
    365          A good place to register stanza handlers and disco features.
    366
    367          [should be overriden in derived classes]
    368
    369          By default: set disco#info and disco#items handlers."""
    370          self.__logger.debug("Setting up Disco handlers...")
    371          self.stream.set_iq_get_handler("query","http://jabber.org/protocol/disco#items",
    372                                      self.__disco_items)
    373          self.stream.set_iq_get_handler("query","http://jabber.org/protocol/disco#info",
    374                                      self.__disco_info)
    375
    376 -    def authorized(self):
    377          """Handle successful authorization event."""
    378          pass
    379
    380 -    def disco_get_info(self,node,iq):
    381          """Get disco#info data for a node.
    382
    383          [may be overriden in derived classes]
    384
    385          By default: return `self.disco_info` if no specific node name
    386          is provided.
    387
    388          :Parameters:
    389              - `node`: name of the node queried.
    390              - `iq`: the stanza received.
    391          :Types:
    392              - `node`: `unicode`
    393              - `iq`: `pyxmpp.Iq`"""
    394          to=iq.get_to()
    395          if to and to!=self.jid:
    396              return iq.make_error_response("recipient-unavailable")
    397          if not node and self.disco_info:
    398              return self.disco_info
    399          return None
    400
    401 -    def disco_get_items(self,node,iq):
    402          """Get disco#items data for a node.
    403
    404          [may be overriden in derived classes]
    405
    406          By default: return `self.disco_items` if no specific node name
    407          is provided.
    408
    409          :Parameters:
    410              - `node`: name of the node queried.
    411              - `iq`: the stanza received.
    412          :Types:
    413              - `node`: `unicode`
    414              - `iq`: `pyxmpp.Iq`"""
    415          to=iq.get_to()
    416          if to and to!=self.jid:
    417              return iq.make_error_response("recipient-unavailable")
    418          if not node and self.disco_items:
    419              return self.disco_items
    420          return None
    421
    422 -    def disconnected(self):
    423          """Handle stream disconnection (connection closed by peer) event.
    424
    425          [may be overriden in derived classes]
    426
    427          By default: do nothing."""
    428          pass
    429
    430  # vi: sts=4 et sw=4
    431