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