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