source: wokkel/pubsub.py @ 94:c0473c62802d

Last change on this file since 94:c0473c62802d was 94:c0473c62802d, checked in by Ralph Meijer <ralphm@…>, 10 years ago

Catch KeyError? for unsupported verbs in legacy PubSubService? usage.

Author: ralphm.
Fixes: #70.

Because requests are now parsed by PubSubRequest?, it can happen that a request
is succesfully parsed, but there is no legacy handler available. This used
to raise a KeyError?, resulting in a internal-server-error being returned.
Now it returns a feature-not-implemented error.

File size: 49.0 KB
RevLine 
[1]1# -*- test-case-name: wokkel.test.test_pubsub -*-
2#
[92]3# Copyright (c) 2003-2011 Ralph Meijer
[1]4# See LICENSE for details.
5
6"""
7XMPP publish-subscribe protocol.
8
9This protocol is specified in
10U{XEP-0060<http://www.xmpp.org/extensions/xep-0060.html>}.
11"""
12
13from zope.interface import implements
14
[19]15from twisted.internet import defer
[59]16from twisted.python import log
[63]17from twisted.words.protocols.jabber import jid, error
[1]18from twisted.words.xish import domish
19
[57]20from wokkel import disco, data_form, generic, shim
[63]21from wokkel.compat import IQ
[1]22from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
[59]23from wokkel.iwokkel import IPubSubClient, IPubSubService, IPubSubResource
[1]24
25# Iq get and set XPath queries
26IQ_GET = '/iq[@type="get"]'
27IQ_SET = '/iq[@type="set"]'
28
29# Publish-subscribe namespaces
30NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
31NS_PUBSUB_EVENT = NS_PUBSUB + '#event'
32NS_PUBSUB_ERRORS = NS_PUBSUB + '#errors'
33NS_PUBSUB_OWNER = NS_PUBSUB + "#owner"
34NS_PUBSUB_NODE_CONFIG = NS_PUBSUB + "#node_config"
35NS_PUBSUB_META_DATA = NS_PUBSUB + "#meta-data"
[57]36NS_PUBSUB_SUBSCRIBE_OPTIONS = NS_PUBSUB + "#subscribe_options"
[1]37
[57]38# XPath to match pubsub requests
39PUBSUB_REQUEST = '/iq[@type="get" or @type="set"]/' + \
40                    'pubsub[@xmlns="' + NS_PUBSUB + '" or ' + \
41                           '@xmlns="' + NS_PUBSUB_OWNER + '"]'
[1]42
[2]43class SubscriptionPending(Exception):
44    """
45    Raised when the requested subscription is pending acceptance.
46    """
47
48
[18]49
[2]50class SubscriptionUnconfigured(Exception):
51    """
52    Raised when the requested subscription needs to be configured before
53    becoming active.
54    """
55
56
[18]57
[1]58class PubSubError(error.StanzaError):
[2]59    """
60    Exception with publish-subscribe specific condition.
61    """
[1]62    def __init__(self, condition, pubsubCondition, feature=None, text=None):
63        appCondition = domish.Element((NS_PUBSUB_ERRORS, pubsubCondition))
64        if feature:
65            appCondition['feature'] = feature
66        error.StanzaError.__init__(self, condition,
67                                         text=text,
68                                         appCondition=appCondition)
69
[2]70
[18]71
[57]72class BadRequest(error.StanzaError):
[30]73    """
74    Bad request stanza error.
75    """
76    def __init__(self, pubsubCondition=None, text=None):
[57]77        if pubsubCondition:
78            appCondition = domish.Element((NS_PUBSUB_ERRORS, pubsubCondition))
79        else:
80            appCondition = None
81        error.StanzaError.__init__(self, 'bad-request',
82                                         text=text,
83                                         appCondition=appCondition)
[30]84
85
86
[1]87class Unsupported(PubSubError):
88    def __init__(self, feature, text=None):
[59]89        self.feature = feature
[1]90        PubSubError.__init__(self, 'feature-not-implemented',
91                                   'unsupported',
92                                   feature,
93                                   text)
94
[59]95    def __str__(self):
96        message = PubSubError.__str__(self)
97        message += ', feature %r' % self.feature
98        return message
[2]99
[18]100
[30]101class Subscription(object):
102    """
103    A subscription to a node.
104
[84]105    @ivar nodeIdentifier: The identifier of the node subscribed to.  The root
106        node is denoted by C{None}.
107    @type nodeIdentifier: C{unicode}
108
[30]109    @ivar subscriber: The subscribing entity.
[84]110    @type subscriber: L{jid.JID}
111
[30]112    @ivar state: The subscription state. One of C{'subscribed'}, C{'pending'},
113                 C{'unconfigured'}.
[84]114    @type state: C{unicode}
115
[30]116    @ivar options: Optional list of subscription options.
[84]117    @type options: C{dict}
118
119    @ivar subscriptionIdentifier: Optional subscription identifier.
120    @type subscriptionIdentifier: C{unicode}
[30]121    """
122
[84]123    def __init__(self, nodeIdentifier, subscriber, state, options=None,
124                       subscriptionIdentifier=None):
[30]125        self.nodeIdentifier = nodeIdentifier
126        self.subscriber = subscriber
127        self.state = state
128        self.options = options or {}
[84]129        self.subscriptionIdentifier = subscriptionIdentifier
130
131
132    @staticmethod
133    def fromElement(element):
134        return Subscription(
135                element.getAttribute('node'),
136                jid.JID(element.getAttribute('jid')),
137                element.getAttribute('subscription'),
138                subscriptionIdentifier=element.getAttribute('subid'))
139
140
141    def toElement(self):
142        """
143        Return the DOM representation of this subscription.
144
145        @rtype: L{domish.Element}
146        """
147        element = domish.Element((None, 'subscription'))
148        if self.nodeIdentifier:
149            element['node'] = self.nodeIdentifier
150        element['jid'] = unicode(self.subscriber)
151        element['subscription'] = self.state
152        if self.subscriptionIdentifier:
153            element['subid'] = self.subscriptionIdentifier
154        return element
[1]155
[2]156
[18]157
[2]158class Item(domish.Element):
159    """
160    Publish subscribe item.
161
162    This behaves like an object providing L{domish.IElement}.
163
164    Item payload can be added using C{addChild} or C{addRawXml}, or using the
165    C{payload} keyword argument to C{__init__}.
166    """
167
168    def __init__(self, id=None, payload=None):
169        """
170        @param id: optional item identifier
171        @type id: L{unicode}
172        @param payload: optional item payload. Either as a domish element, or
173                        as serialized XML.
174        @type payload: object providing L{domish.IElement} or L{unicode}.
175        """
176
[86]177        domish.Element.__init__(self, (None, 'item'))
[2]178        if id is not None:
179            self['id'] = id
180        if payload is not None:
181            if isinstance(payload, basestring):
182                self.addRawXml(payload)
183            else:
184                self.addChild(payload)
185
[10]186
[18]187
[57]188class PubSubRequest(generic.Stanza):
[2]189    """
[57]190    A publish-subscribe request.
[2]191
[57]192    The set of instance variables used depends on the type of request. If
193    a variable is not applicable or not passed in the request, its value is
194    C{None}.
195
196    @ivar verb: The type of publish-subscribe request. See L{_requestVerbMap}.
197    @type verb: C{str}.
198
199    @ivar affiliations: Affiliations to be modified.
200    @type affiliations: C{set}
201    @ivar items: The items to be published, as L{domish.Element}s.
202    @type items: C{list}
203    @ivar itemIdentifiers: Identifiers of the items to be retrieved or
204                           retracted.
205    @type itemIdentifiers: C{set}
206    @ivar maxItems: Maximum number of items to retrieve.
207    @type maxItems: C{int}.
208    @ivar nodeIdentifier: Identifier of the node the request is about.
209    @type nodeIdentifier: C{unicode}
210    @ivar nodeType: The type of node that should be created, or for which the
211                    configuration is retrieved. C{'leaf'} or C{'collection'}.
212    @type nodeType: C{str}
213    @ivar options: Configurations options for nodes, subscriptions and publish
214                   requests.
215    @type options: L{data_form.Form}
216    @ivar subscriber: The subscribing entity.
217    @type subscriber: L{JID}
218    @ivar subscriptionIdentifier: Identifier for a specific subscription.
219    @type subscriptionIdentifier: C{unicode}
220    @ivar subscriptions: Subscriptions to be modified, as a set of
221                         L{Subscription}.
222    @type subscriptions: C{set}
[93]223    @ivar affiliations: Affiliations to be modified, as a dictionary of entity
224                        (L{JID} to affiliation (C{unicode}).
225    @type affiliations: C{dict}
[2]226    """
227
[57]228    verb = None
[2]229
[57]230    affiliations = None
231    items = None
232    itemIdentifiers = None
233    maxItems = None
234    nodeIdentifier = None
235    nodeType = None
236    options = None
237    subscriber = None
238    subscriptionIdentifier = None
239    subscriptions = None
[93]240    affiliations = None
[2]241
[57]242    # Map request iq type and subelement name to request verb
243    _requestVerbMap = {
244        ('set', NS_PUBSUB, 'publish'): 'publish',
245        ('set', NS_PUBSUB, 'subscribe'): 'subscribe',
246        ('set', NS_PUBSUB, 'unsubscribe'): 'unsubscribe',
247        ('get', NS_PUBSUB, 'options'): 'optionsGet',
248        ('set', NS_PUBSUB, 'options'): 'optionsSet',
249        ('get', NS_PUBSUB, 'subscriptions'): 'subscriptions',
250        ('get', NS_PUBSUB, 'affiliations'): 'affiliations',
251        ('set', NS_PUBSUB, 'create'): 'create',
252        ('get', NS_PUBSUB_OWNER, 'default'): 'default',
253        ('get', NS_PUBSUB_OWNER, 'configure'): 'configureGet',
254        ('set', NS_PUBSUB_OWNER, 'configure'): 'configureSet',
255        ('get', NS_PUBSUB, 'items'): 'items',
256        ('set', NS_PUBSUB, 'retract'): 'retract',
257        ('set', NS_PUBSUB_OWNER, 'purge'): 'purge',
258        ('set', NS_PUBSUB_OWNER, 'delete'): 'delete',
259        ('get', NS_PUBSUB_OWNER, 'affiliations'): 'affiliationsGet',
260        ('set', NS_PUBSUB_OWNER, 'affiliations'): 'affiliationsSet',
261        ('get', NS_PUBSUB_OWNER, 'subscriptions'): 'subscriptionsGet',
262        ('set', NS_PUBSUB_OWNER, 'subscriptions'): 'subscriptionsSet',
263    }
[25]264
[57]265    # Map request verb to request iq type and subelement name
266    _verbRequestMap = dict(((v, k) for k, v in _requestVerbMap.iteritems()))
267
268    # Map request verb to parameter handler names
269    _parameters = {
270        'publish': ['node', 'items'],
[83]271        'subscribe': ['nodeOrEmpty', 'jid', 'optionsWithSubscribe'],
[84]272        'unsubscribe': ['nodeOrEmpty', 'jid', 'subidOrNone'],
273        'optionsGet': ['nodeOrEmpty', 'jid', 'subidOrNone'],
274        'optionsSet': ['nodeOrEmpty', 'jid', 'options', 'subidOrNone'],
[57]275        'subscriptions': [],
276        'affiliations': [],
[82]277        'create': ['nodeOrNone', 'configureOrNone'],
[57]278        'default': ['default'],
279        'configureGet': ['nodeOrEmpty'],
280        'configureSet': ['nodeOrEmpty', 'configure'],
[84]281        'items': ['node', 'maxItems', 'itemIdentifiers', 'subidOrNone'],
[57]282        'retract': ['node', 'itemIdentifiers'],
283        'purge': ['node'],
284        'delete': ['node'],
[59]285        'affiliationsGet': ['nodeOrEmpty'],
[93]286        'affiliationsSet': ['nodeOrEmpty', 'affiliations'],
[59]287        'subscriptionsGet': ['nodeOrEmpty'],
[57]288        'subscriptionsSet': [],
289    }
290
291    def __init__(self, verb=None):
292        self.verb = verb
293
294
295    def _parse_node(self, verbElement):
[10]296        """
[57]297        Parse the required node identifier out of the verbElement.
298        """
299        try:
300            self.nodeIdentifier = verbElement["node"]
301        except KeyError:
302            raise BadRequest('nodeid-required')
303
304
305    def _render_node(self, verbElement):
306        """
307        Render the required node identifier on the verbElement.
308        """
309        if not self.nodeIdentifier:
310            raise Exception("Node identifier is required")
311
312        verbElement['node'] = self.nodeIdentifier
313
314
315    def _parse_nodeOrEmpty(self, verbElement):
316        """
317        Parse the node identifier out of the verbElement. May be empty.
318        """
319        self.nodeIdentifier = verbElement.getAttribute("node", '')
320
321
322    def _render_nodeOrEmpty(self, verbElement):
323        """
324        Render the node identifier on the verbElement. May be empty.
325        """
326        if self.nodeIdentifier:
327            verbElement['node'] = self.nodeIdentifier
328
329
330    def _parse_nodeOrNone(self, verbElement):
331        """
332        Parse the optional node identifier out of the verbElement.
333        """
334        self.nodeIdentifier = verbElement.getAttribute("node")
335
336
337    def _render_nodeOrNone(self, verbElement):
338        """
339        Render the optional node identifier on the verbElement.
340        """
341        if self.nodeIdentifier:
342            verbElement['node'] = self.nodeIdentifier
343
344
345    def _parse_items(self, verbElement):
346        """
347        Parse items out of the verbElement for publish requests.
348        """
349        self.items = []
350        for element in verbElement.elements():
351            if element.uri == NS_PUBSUB and element.name == 'item':
352                self.items.append(element)
353
354
355    def _render_items(self, verbElement):
356        """
357        Render items into the verbElement for publish requests.
358        """
359        if self.items:
360            for item in self.items:
361                verbElement.addChild(item)
362
363
364    def _parse_jid(self, verbElement):
365        """
366        Parse subscriber out of the verbElement for un-/subscribe requests.
367        """
368        try:
369            self.subscriber = jid.internJID(verbElement["jid"])
370        except KeyError:
371            raise BadRequest('jid-required')
372
373
374    def _render_jid(self, verbElement):
375        """
376        Render subscriber into the verbElement for un-/subscribe requests.
377        """
378        verbElement['jid'] = self.subscriber.full()
379
380
381    def _parse_default(self, verbElement):
382        """
383        Parse node type out of a request for the default node configuration.
384        """
[80]385        form = data_form.findForm(verbElement, NS_PUBSUB_NODE_CONFIG)
[57]386        if form and form.formType == 'submit':
387            values = form.getValues()
388            self.nodeType = values.get('pubsub#node_type', 'leaf')
389        else:
390            self.nodeType = 'leaf'
391
392
393    def _parse_configure(self, verbElement):
394        """
395        Parse options out of a request for setting the node configuration.
396        """
[80]397        form = data_form.findForm(verbElement, NS_PUBSUB_NODE_CONFIG)
[57]398        if form:
[81]399            if form.formType in ('submit', 'cancel'):
400                self.options = form
[57]401            else:
[81]402                raise BadRequest(text=u"Unexpected form type '%s'" % form.formType)
[57]403        else:
404            raise BadRequest(text="Missing configuration form")
405
406
[82]407    def _parse_configureOrNone(self, verbElement):
408        """
409        Parse optional node configuration form in create request.
410        """
411        for element in verbElement.parent.elements():
412            if element.uri == NS_PUBSUB and element.name == 'configure':
413                form = data_form.findForm(element, NS_PUBSUB_NODE_CONFIG)
414                if form:
[83]415                    if form.formType != 'submit':
[82]416                        raise BadRequest(text=u"Unexpected form type '%s'" %
417                                              form.formType)
418                else:
419                    form = data_form.Form('submit',
420                                          formNamespace=NS_PUBSUB_NODE_CONFIG)
[83]421                self.options = form
[82]422
423
424    def _render_configureOrNone(self, verbElement):
425        """
426        Render optional node configuration form in create request.
427        """
428        if self.options is not None:
429            configure = verbElement.parent.addElement('configure')
430            configure.addChild(self.options.toElement())
431
[57]432
433    def _parse_itemIdentifiers(self, verbElement):
434        """
435        Parse item identifiers out of items and retract requests.
436        """
437        self.itemIdentifiers = []
438        for element in verbElement.elements():
439            if element.uri == NS_PUBSUB and element.name == 'item':
440                try:
441                    self.itemIdentifiers.append(element["id"])
442                except KeyError:
443                    raise BadRequest()
444
445
446    def _render_itemIdentifiers(self, verbElement):
447        """
448        Render item identifiers into items and retract requests.
449        """
450        if self.itemIdentifiers:
451            for itemIdentifier in self.itemIdentifiers:
452                item = verbElement.addElement('item')
453                item['id'] = itemIdentifier
454
455
456    def _parse_maxItems(self, verbElement):
457        """
458        Parse maximum items out of an items request.
459        """
460        value = verbElement.getAttribute('max_items')
461
462        if value:
463            try:
464                self.maxItems = int(value)
465            except ValueError:
466                raise BadRequest(text="Field max_items requires a positive " +
467                                      "integer value")
468
469
470    def _render_maxItems(self, verbElement):
471        """
[84]472        Render maximum items into an items request.
[57]473        """
474        if self.maxItems:
475            verbElement['max_items'] = unicode(self.maxItems)
476
477
[84]478    def _parse_subidOrNone(self, verbElement):
479        """
480        Parse subscription identifier out of a request.
481        """
482        self.subscriptionIdentifier = verbElement.getAttribute("subid")
483
484
485    def _render_subidOrNone(self, verbElement):
486        """
487        Render subscription identifier into a request.
488        """
489        if self.subscriptionIdentifier:
490            verbElement['subid'] = self.subscriptionIdentifier
491
492
[57]493    def _parse_options(self, verbElement):
[81]494        """
495        Parse options form out of a subscription options request.
496        """
[80]497        form = data_form.findForm(verbElement, NS_PUBSUB_SUBSCRIBE_OPTIONS)
[57]498        if form:
[81]499            if form.formType in ('submit', 'cancel'):
500                self.options = form
[57]501            else:
[81]502                raise BadRequest(text=u"Unexpected form type '%s'" % form.formType)
[57]503        else:
504            raise BadRequest(text="Missing options form")
505
[81]506
[83]507
508    def _render_options(self, verbElement):
509        verbElement.addChild(self.options.toElement())
510
511
512    def _parse_optionsWithSubscribe(self, verbElement):
513        for element in verbElement.parent.elements():
514            if element.name == 'options' and element.uri == NS_PUBSUB:
515                form = data_form.findForm(element,
516                                          NS_PUBSUB_SUBSCRIBE_OPTIONS)
517                if form:
518                    if form.formType != 'submit':
519                        raise BadRequest(text=u"Unexpected form type '%s'" %
520                                              form.formType)
521                else:
522                    form = data_form.Form('submit',
523                                          formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
524                self.options = form
525
526
527    def _render_optionsWithSubscribe(self, verbElement):
528        if self.options:
529            optionsElement = verbElement.parent.addElement('options')
530            self._render_options(optionsElement)
531
532
[93]533    def _parse_affiliations(self, verbElement):
534        self.affiliations = {}
535        for element in verbElement.elements():
536            if (element.uri == NS_PUBSUB_OWNER and
537                element.name == 'affiliation'):
538                try:
539                    entity = jid.internJID(element['jid']).userhostJID()
540                except KeyError:
541                    raise BadRequest(text='Missing jid attribute')
542
543                if entity in self.affiliations:
544                    raise BadRequest(text='Multiple affiliations for an entity')
545
546                try:
547                    affiliation = element['affiliation']
548                except KeyError:
549                    raise BadRequest(text='Missing affiliation attribute')
550
551                self.affiliations[entity] = affiliation
552
553
[57]554    def parseElement(self, element):
555        """
556        Parse the publish-subscribe verb and parameters out of a request.
557        """
558        generic.Stanza.parseElement(self, element)
559
[83]560        verbs = []
[92]561        verbElements = []
[57]562        for child in element.pubsub.elements():
563            key = (self.stanzaType, child.uri, child.name)
564            try:
565                verb = self._requestVerbMap[key]
566            except KeyError:
567                continue
568
[83]569            verbs.append(verb)
[92]570            verbElements.append(child)
[83]571
572        if not verbs:
[57]573            raise NotImplementedError()
574
[83]575        if len(verbs) > 1:
576            if 'optionsSet' in verbs and 'subscribe' in verbs:
577                self.verb = 'subscribe'
[92]578                verbElement = verbElements[verbs.index('subscribe')]
[83]579            else:
580                raise NotImplementedError()
581        else:
582            self.verb = verbs[0]
[92]583            verbElement = verbElements[0]
[83]584
585        for parameter in self._parameters[self.verb]:
[92]586            getattr(self, '_parse_%s' % parameter)(verbElement)
[57]587
588
[83]589
[57]590    def send(self, xs):
591        """
592        Send this request to its recipient.
593
594        This renders all of the relevant parameters for this specific
[63]595        requests into an L{IQ}, and invoke its C{send} method.
[57]596        This returns a deferred that fires upon reception of a response. See
[63]597        L{IQ} for details.
[57]598
599        @param xs: The XML stream to send the request on.
600        @type xs: L{xmlstream.XmlStream}
601        @rtype: L{defer.Deferred}.
602        """
603
604        try:
605            (self.stanzaType,
606             childURI,
607             childName) = self._verbRequestMap[self.verb]
608        except KeyError:
609            raise NotImplementedError()
610
[63]611        iq = IQ(xs, self.stanzaType)
[57]612        iq.addElement((childURI, 'pubsub'))
613        verbElement = iq.pubsub.addElement(childName)
614
615        if self.sender:
616            iq['from'] = self.sender.full()
617        if self.recipient:
618            iq['to'] = self.recipient.full()
619
620        for parameter in self._parameters[self.verb]:
621            getattr(self, '_render_%s' % parameter)(verbElement)
622
623        return iq.send()
[2]624
[10]625
626
[27]627class PubSubEvent(object):
628    """
629    A publish subscribe event.
630
631    @param sender: The entity from which the notification was received.
632    @type sender: L{jid.JID}
633    @param recipient: The entity to which the notification was sent.
634    @type recipient: L{wokkel.pubsub.ItemsEvent}
635    @param nodeIdentifier: Identifier of the node the event pertains to.
636    @type nodeIdentifier: C{unicode}
637    @param headers: SHIM headers, see L{wokkel.shim.extractHeaders}.
638    @type headers: L{dict}
639    """
640
641    def __init__(self, sender, recipient, nodeIdentifier, headers):
642        self.sender = sender
643        self.recipient = recipient
644        self.nodeIdentifier = nodeIdentifier
645        self.headers = headers
646
647
648
649class ItemsEvent(PubSubEvent):
650    """
651    A publish-subscribe event that signifies new, updated and retracted items.
652
653    @param items: List of received items as domish elements.
654    @type items: C{list} of L{domish.Element}
655    """
656
657    def __init__(self, sender, recipient, nodeIdentifier, items, headers):
658        PubSubEvent.__init__(self, sender, recipient, nodeIdentifier, headers)
659        self.items = items
660
661
662
663class DeleteEvent(PubSubEvent):
664    """
665    A publish-subscribe event that signifies the deletion of a node.
666    """
667
[43]668    redirectURI = None
669
[27]670
671
672class PurgeEvent(PubSubEvent):
673    """
674    A publish-subscribe event that signifies the purging of a node.
675    """
676
677
678
[2]679class PubSubClient(XMPPHandler):
680    """
681    Publish subscribe client protocol.
682    """
683
684    implements(IPubSubClient)
685
686    def connectionInitialized(self):
[13]687        self.xmlstream.addObserver('/message/event[@xmlns="%s"]' %
688                                   NS_PUBSUB_EVENT, self._onEvent)
[2]689
[25]690
[13]691    def _onEvent(self, message):
[90]692        if message.getAttribute('type') == 'error':
693            return
694
[2]695        try:
[27]696            sender = jid.JID(message["from"])
[6]697            recipient = jid.JID(message["to"])
[2]698        except KeyError:
699            return
700
[15]701        actionElement = None
[13]702        for element in message.event.elements():
703            if element.uri == NS_PUBSUB_EVENT:
704                actionElement = element
705
706        if not actionElement:
707            return
708
709        eventHandler = getattr(self, "_onEvent_%s" % actionElement.name, None)
710
711        if eventHandler:
[27]712            headers = shim.extractHeaders(message)
713            eventHandler(sender, recipient, actionElement, headers)
[13]714            message.handled = True
715
[30]716
[27]717    def _onEvent_items(self, sender, recipient, action, headers):
[13]718        nodeIdentifier = action["node"]
719
720        items = [element for element in action.elements()
721                         if element.name in ('item', 'retract')]
[2]722
[27]723        event = ItemsEvent(sender, recipient, nodeIdentifier, items, headers)
724        self.itemsReceived(event)
[2]725
[30]726
[27]727    def _onEvent_delete(self, sender, recipient, action, headers):
[13]728        nodeIdentifier = action["node"]
[27]729        event = DeleteEvent(sender, recipient, nodeIdentifier, headers)
[43]730        if action.redirect:
731            event.redirectURI = action.redirect.getAttribute('uri')
[27]732        self.deleteReceived(event)
[13]733
[30]734
[27]735    def _onEvent_purge(self, sender, recipient, action, headers):
[13]736        nodeIdentifier = action["node"]
[27]737        event = PurgeEvent(sender, recipient, nodeIdentifier, headers)
738        self.purgeReceived(event)
[13]739
[30]740
[27]741    def itemsReceived(self, event):
[2]742        pass
743
[30]744
[27]745    def deleteReceived(self, event):
[13]746        pass
747
[30]748
[27]749    def purgeReceived(self, event):
[13]750        pass
751
[30]752
[82]753    def createNode(self, service, nodeIdentifier=None, options=None,
754                         sender=None):
[18]755        """
756        Create a publish subscribe node.
757
758        @param service: The publish subscribe service to create the node at.
759        @type service: L{JID}
760        @param nodeIdentifier: Optional suggestion for the id of the node.
761        @type nodeIdentifier: C{unicode}
[82]762        @param options: Optional node configuration options.
763        @type options: C{dict}
[18]764        """
[57]765        request = PubSubRequest('create')
766        request.recipient = service
767        request.nodeIdentifier = nodeIdentifier
[58]768        request.sender = sender
[2]769
[82]770        if options:
771            form = data_form.Form(formType='submit',
772                                  formNamespace=NS_PUBSUB_NODE_CONFIG)
773            form.makeFields(options)
774            request.options = form
775
[2]776        def cb(iq):
777            try:
778                new_node = iq.pubsub.create["node"]
779            except AttributeError:
780                # the suggested node identifier was accepted
[6]781                new_node = nodeIdentifier
[2]782            return new_node
783
[57]784        d = request.send(self.xmlstream)
785        d.addCallback(cb)
786        return d
[2]787
[25]788
[58]789    def deleteNode(self, service, nodeIdentifier, sender=None):
[18]790        """
791        Delete a publish subscribe node.
792
793        @param service: The publish subscribe service to delete the node from.
794        @type service: L{JID}
795        @param nodeIdentifier: The identifier of the node.
796        @type nodeIdentifier: C{unicode}
797        """
[57]798        request = PubSubRequest('delete')
799        request.recipient = service
800        request.nodeIdentifier = nodeIdentifier
[58]801        request.sender = sender
[57]802        return request.send(self.xmlstream)
[2]803
[25]804
[83]805    def subscribe(self, service, nodeIdentifier, subscriber,
806                        options=None, sender=None):
[18]807        """
808        Subscribe to a publish subscribe node.
809
810        @param service: The publish subscribe service that keeps the node.
811        @type service: L{JID}
[84]812
[18]813        @param nodeIdentifier: The identifier of the node.
814        @type nodeIdentifier: C{unicode}
[84]815
[18]816        @param subscriber: The entity to subscribe to the node. This entity
[84]817            will get notifications of new published items.
[18]818        @type subscriber: L{JID}
[84]819
[83]820        @param options: Subscription options.
[84]821        @type options: C{dict}
822
823        @return: Deferred that fires with L{Subscription} or errbacks with
824            L{SubscriptionPending} or L{SubscriptionUnconfigured}.
825        @rtype: L{defer.Deferred}
[18]826        """
[57]827        request = PubSubRequest('subscribe')
828        request.recipient = service
829        request.nodeIdentifier = nodeIdentifier
830        request.subscriber = subscriber
[58]831        request.sender = sender
[2]832
[83]833        if options:
834            form = data_form.Form(formType='submit',
835                                  formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
836            form.makeFields(options)
837            request.options = form
838
[2]839        def cb(iq):
[84]840            subscription = Subscription.fromElement(iq.pubsub.subscription)
[2]841
[84]842            if subscription.state == 'pending':
843                raise SubscriptionPending()
844            elif subscription.state == 'unconfigured':
845                raise SubscriptionUnconfigured()
[2]846            else:
847                # we assume subscription == 'subscribed'
848                # any other value would be invalid, but that should have
849                # yielded a stanza error.
[84]850                return subscription
[2]851
[57]852        d = request.send(self.xmlstream)
853        d.addCallback(cb)
854        return d
[2]855
[25]856
[84]857    def unsubscribe(self, service, nodeIdentifier, subscriber,
858                          subscriptionIdentifier=None, sender=None):
[18]859        """
860        Unsubscribe from a publish subscribe node.
861
862        @param service: The publish subscribe service that keeps the node.
863        @type service: L{JID}
[84]864
[18]865        @param nodeIdentifier: The identifier of the node.
866        @type nodeIdentifier: C{unicode}
[84]867
[18]868        @param subscriber: The entity to unsubscribe from the node.
869        @type subscriber: L{JID}
[84]870
871        @param subscriptionIdentifier: Optional subscription identifier.
872        @type subscriptionIdentifier: C{unicode}
[18]873        """
[57]874        request = PubSubRequest('unsubscribe')
875        request.recipient = service
876        request.nodeIdentifier = nodeIdentifier
877        request.subscriber = subscriber
[84]878        request.subscriptionIdentifier = subscriptionIdentifier
[58]879        request.sender = sender
[57]880        return request.send(self.xmlstream)
[10]881
[25]882
[58]883    def publish(self, service, nodeIdentifier, items=None, sender=None):
[18]884        """
885        Publish to a publish subscribe node.
886
887        @param service: The publish subscribe service that keeps the node.
888        @type service: L{JID}
889        @param nodeIdentifier: The identifier of the node.
890        @type nodeIdentifier: C{unicode}
891        @param items: Optional list of L{Item}s to publish.
892        @type items: C{list}
893        """
[57]894        request = PubSubRequest('publish')
895        request.recipient = service
896        request.nodeIdentifier = nodeIdentifier
897        request.items = items
[58]898        request.sender = sender
[57]899        return request.send(self.xmlstream)
[2]900
[25]901
[84]902    def items(self, service, nodeIdentifier, maxItems=None,
903              subscriptionIdentifier=None, sender=None):
[18]904        """
905        Retrieve previously published items from a publish subscribe node.
906
907        @param service: The publish subscribe service that keeps the node.
908        @type service: L{JID}
[84]909
[18]910        @param nodeIdentifier: The identifier of the node.
911        @type nodeIdentifier: C{unicode}
[84]912
[18]913        @param maxItems: Optional limit on the number of retrieved items.
914        @type maxItems: C{int}
[84]915
916        @param subscriptionIdentifier: Optional subscription identifier. In
917            case the node has been subscribed to multiple times, this narrows
918            the results to the specific subscription.
919        @type subscriptionIdentifier: C{unicode}
[18]920        """
[57]921        request = PubSubRequest('items')
922        request.recipient = service
923        request.nodeIdentifier = nodeIdentifier
[18]924        if maxItems:
[57]925            request.maxItems = str(int(maxItems))
[84]926        request.subscriptionIdentifier = subscriptionIdentifier
[58]927        request.sender = sender
[18]928
[17]929        def cb(iq):
930            items = []
931            for element in iq.pubsub.items.elements():
932                if element.uri == NS_PUBSUB and element.name == 'item':
933                    items.append(element)
934            return items
935
[57]936        d = request.send(self.xmlstream)
937        d.addCallback(cb)
938        return d
[10]939
[18]940
[84]941    def getOptions(self, service, nodeIdentifier, subscriber,
942                         subscriptionIdentifier=None, sender=None):
[83]943        """
944        Get subscription options.
945
946        @param service: The publish subscribe service that keeps the node.
947        @type service: L{JID}
948
949        @param nodeIdentifier: The identifier of the node.
950        @type nodeIdentifier: C{unicode}
951
952        @param subscriber: The entity subscribed to the node.
953        @type subscriber: L{JID}
954
[84]955        @param subscriptionIdentifier: Optional subscription identifier.
956        @type subscriptionIdentifier: C{unicode}
957
[83]958        @rtype: L{data_form.Form}
959        """
960        request = PubSubRequest('optionsGet')
961        request.recipient = service
962        request.nodeIdentifier = nodeIdentifier
963        request.subscriber = subscriber
[84]964        request.subscriptionIdentifier = subscriptionIdentifier
[83]965        request.sender = sender
966
967        def cb(iq):
968            form = data_form.findForm(iq.pubsub.options,
969                                      NS_PUBSUB_SUBSCRIBE_OPTIONS)
970            form.typeCheck()
971            return form
972
973        d = request.send(self.xmlstream)
974        d.addCallback(cb)
975        return d
976
977
978    def setOptions(self, service, nodeIdentifier, subscriber,
[84]979                         options, subscriptionIdentifier=None, sender=None):
[83]980        """
981        Set subscription options.
982
983        @param service: The publish subscribe service that keeps the node.
984        @type service: L{JID}
985
986        @param nodeIdentifier: The identifier of the node.
987        @type nodeIdentifier: C{unicode}
988
989        @param subscriber: The entity subscribed to the node.
990        @type subscriber: L{JID}
991
992        @param options: Subscription options.
993        @type options: C{dict}.
[84]994
995        @param subscriptionIdentifier: Optional subscription identifier.
996        @type subscriptionIdentifier: C{unicode}
[83]997        """
998        request = PubSubRequest('optionsSet')
999        request.recipient = service
1000        request.nodeIdentifier = nodeIdentifier
1001        request.subscriber = subscriber
[84]1002        request.subscriptionIdentifier = subscriptionIdentifier
[83]1003        request.sender = sender
1004
1005        form = data_form.Form(formType='submit',
1006                              formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
1007        form.makeFields(options)
1008        request.options = form
1009
1010        d = request.send(self.xmlstream)
1011        return d
1012
1013
[18]1014
[1]1015class PubSubService(XMPPHandler, IQHandlerMixin):
1016    """
1017    Protocol implementation for a XMPP Publish Subscribe Service.
1018
1019    The word Service here is used as taken from the Publish Subscribe
1020    specification. It is the party responsible for keeping nodes and their
1021    subscriptions, and sending out notifications.
1022
1023    Methods from the L{IPubSubService} interface that are called as
1024    a result of an XMPP request may raise exceptions. Alternatively the
1025    deferred returned by these methods may have their errback called. These are
1026    handled as follows:
1027
[22]1028     - If the exception is an instance of L{error.StanzaError}, an error
1029       response iq is returned.
1030     - Any other exception is reported using L{log.msg}. An error response
1031       with the condition C{internal-server-error} is returned.
[1]1032
1033    The default implementation of said methods raises an L{Unsupported}
1034    exception and are meant to be overridden.
1035
1036    @ivar discoIdentity: Service discovery identity as a dictionary with
1037                         keys C{'category'}, C{'type'} and C{'name'}.
1038    @ivar pubSubFeatures: List of supported publish-subscribe features for
1039                          service discovery, as C{str}.
[22]1040    @type pubSubFeatures: C{list} or C{None}
[1]1041    """
1042
[88]1043    implements(IPubSubService, disco.IDisco)
[1]1044
1045    iqHandlers = {
[57]1046            '/*': '_onPubSubRequest',
[1]1047            }
1048
[59]1049    _legacyHandlers = {
1050        'publish': ('publish', ['sender', 'recipient',
1051                                'nodeIdentifier', 'items']),
1052        'subscribe': ('subscribe', ['sender', 'recipient',
1053                                    'nodeIdentifier', 'subscriber']),
1054        'unsubscribe': ('unsubscribe', ['sender', 'recipient',
1055                                        'nodeIdentifier', 'subscriber']),
1056        'subscriptions': ('subscriptions', ['sender', 'recipient']),
1057        'affiliations': ('affiliations', ['sender', 'recipient']),
1058        'create': ('create', ['sender', 'recipient', 'nodeIdentifier']),
1059        'getConfigurationOptions': ('getConfigurationOptions', []),
1060        'default': ('getDefaultConfiguration',
1061                    ['sender', 'recipient', 'nodeType']),
1062        'configureGet': ('getConfiguration', ['sender', 'recipient',
1063                                              'nodeIdentifier']),
1064        'configureSet': ('setConfiguration', ['sender', 'recipient',
1065                                              'nodeIdentifier', 'options']),
1066        'items': ('items', ['sender', 'recipient', 'nodeIdentifier',
1067                            'maxItems', 'itemIdentifiers']),
1068        'retract': ('retract', ['sender', 'recipient', 'nodeIdentifier',
1069                                'itemIdentifiers']),
1070        'purge': ('purge', ['sender', 'recipient', 'nodeIdentifier']),
1071        'delete': ('delete', ['sender', 'recipient', 'nodeIdentifier']),
1072    }
[25]1073
[59]1074    hideNodes = False
1075
1076    def __init__(self, resource=None):
1077        self.resource = resource
[1]1078        self.discoIdentity = {'category': 'pubsub',
[87]1079                              'type': 'service',
[1]1080                              'name': 'Generic Publish-Subscribe Service'}
1081
1082        self.pubSubFeatures = []
1083
[25]1084
[1]1085    def connectionMade(self):
[57]1086        self.xmlstream.addObserver(PUBSUB_REQUEST, self.handleRequest)
[1]1087
[25]1088
[88]1089    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
1090        def toInfo(nodeInfo):
[25]1091            if not nodeInfo:
[88]1092                return
[1]1093
[25]1094            (nodeType, metaData) = nodeInfo['type'], nodeInfo['meta-data']
1095            info.append(disco.DiscoIdentity('pubsub', nodeType))
1096            if metaData:
1097                form = data_form.Form(formType="result",
1098                                      formNamespace=NS_PUBSUB_META_DATA)
[29]1099                form.addField(
[25]1100                        data_form.Field(
1101                            var='pubsub#node_type',
1102                            value=nodeType,
1103                            label='The type of node (collection or leaf)'
1104                        )
1105                )
[1]1106
[25]1107                for metaDatum in metaData:
[29]1108                    form.addField(data_form.Field.fromDict(metaDatum))
[1]1109
[52]1110                info.append(form)
[1]1111
[88]1112            return
[59]1113
1114        info = []
1115
1116        request = PubSubRequest('discoInfo')
1117
1118        if self.resource is not None:
1119            resource = self.resource.locateResource(request)
1120            identity = resource.discoIdentity
1121            features = resource.features
1122            getInfo = resource.getInfo
1123        else:
[87]1124            category = self.discoIdentity['category']
1125            idType = self.discoIdentity['type']
1126            name = self.discoIdentity['name']
[59]1127            identity = disco.DiscoIdentity(category, idType, name)
1128            features = self.pubSubFeatures
1129            getInfo = self.getNodeInfo
1130
1131        if not nodeIdentifier:
1132            info.append(identity)
1133            info.append(disco.DiscoFeature(disco.NS_DISCO_ITEMS))
1134            info.extend([disco.DiscoFeature("%s#%s" % (NS_PUBSUB, feature))
1135                         for feature in features])
1136
[87]1137        d = defer.maybeDeferred(getInfo, requestor, target, nodeIdentifier or '')
[88]1138        d.addCallback(toInfo)
[59]1139        d.addErrback(log.err)
[88]1140        d.addCallback(lambda _: info)
[25]1141        return d
1142
[1]1143
[88]1144    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
[59]1145        if self.hideNodes:
1146            d = defer.succeed([])
1147        elif self.resource is not None:
1148            request = PubSubRequest('discoInfo')
1149            resource = self.resource.locateResource(request)
1150            d = resource.getNodes(requestor, target, nodeIdentifier)
1151        elif nodeIdentifier:
1152            d = self.getNodes(requestor, target)
1153        else:
1154            d = defer.succeed([])
1155
[1]1156        d.addCallback(lambda nodes: [disco.DiscoItem(target, node)
1157                                     for node in nodes])
1158        return d
1159
[25]1160
[57]1161    def _onPubSubRequest(self, iq):
1162        request = PubSubRequest.fromElement(iq)
[25]1163
[59]1164        if self.resource is not None:
1165            resource = self.resource.locateResource(request)
1166        else:
1167            resource = self
[25]1168
[59]1169        # Preprocess the request, knowing the handling resource
1170        try:
1171            preProcessor = getattr(self, '_preProcess_%s' % request.verb)
1172        except AttributeError:
1173            pass
1174        else:
1175            request = preProcessor(resource, request)
1176            if request is None:
1177                return defer.succeed(None)
[25]1178
[59]1179        # Process the request itself,
1180        if resource is not self:
1181            try:
1182                handler = getattr(resource, request.verb)
1183            except AttributeError:
1184                text = "Request verb: %s" % request.verb
1185                return defer.fail(Unsupported('', text))
[25]1186
[59]1187            d = handler(request)
1188        else:
[94]1189            try:
1190                handlerName, argNames = self._legacyHandlers[request.verb]
1191            except KeyError:
1192                text = "Request verb: %s" % request.verb
1193                return defer.fail(Unsupported('', text))
1194
[59]1195            handler = getattr(self, handlerName)
[94]1196
[59]1197            args = [getattr(request, arg) for arg in argNames]
[81]1198            if 'options' in argNames:
1199                args[argNames.index('options')] = request.options.getValues()
[94]1200
[59]1201            d = handler(*args)
[1]1202
[59]1203        # If needed, translate the result into a response
1204        try:
1205            cb = getattr(self, '_toResponse_%s' % request.verb)
1206        except AttributeError:
1207            pass
1208        else:
1209            d.addCallback(cb, resource, request)
[1]1210
1211        return d
1212
[25]1213
[59]1214    def _toResponse_subscribe(self, result, resource, request):
1215        response = domish.Element((NS_PUBSUB, "pubsub"))
[92]1216        response.addChild(result.toElement())
[59]1217        return response
[1]1218
1219
[59]1220    def _toResponse_subscriptions(self, result, resource, request):
1221        response = domish.Element((NS_PUBSUB, 'pubsub'))
1222        subscriptions = response.addElement('subscriptions')
1223        for subscription in result:
[84]1224            subscriptions.addChild(subscription.toElement())
[59]1225        return response
[1]1226
[25]1227
[59]1228    def _toResponse_affiliations(self, result, resource, request):
1229        response = domish.Element((NS_PUBSUB, 'pubsub'))
1230        affiliations = response.addElement('affiliations')
[1]1231
[59]1232        for nodeIdentifier, affiliation in result:
1233            item = affiliations.addElement('affiliation')
1234            item['node'] = nodeIdentifier
1235            item['affiliation'] = affiliation
[25]1236
[59]1237        return response
[1]1238
[59]1239
1240    def _toResponse_create(self, result, resource, request):
1241        if not request.nodeIdentifier or request.nodeIdentifier != result:
[1]1242            response = domish.Element((NS_PUBSUB, 'pubsub'))
[59]1243            create = response.addElement('create')
1244            create['node'] = result
[1]1245            return response
[59]1246        else:
1247            return None
[1]1248
1249
[59]1250    def _formFromConfiguration(self, resource, values):
[79]1251        fieldDefs = resource.getConfigurationOptions()
[25]1252        form = data_form.Form(formType="form",
[79]1253                              formNamespace=NS_PUBSUB_NODE_CONFIG)
1254        form.makeFields(values, fieldDefs)
[1]1255        return form
1256
[57]1257
[81]1258    def _checkConfiguration(self, resource, form):
1259        fieldDefs = resource.getConfigurationOptions()
1260        form.typeCheck(fieldDefs, filterUnknown=True)
[29]1261
[25]1262
[82]1263    def _preProcess_create(self, resource, request):
1264        if request.options:
1265            self._checkConfiguration(resource, request.options)
1266        return request
1267
1268
[59]1269    def _preProcess_default(self, resource, request):
1270        if request.nodeType not in ('leaf', 'collection'):
1271            raise error.StanzaError('not-acceptable')
1272        else:
1273            return request
[1]1274
1275
[59]1276    def _toResponse_default(self, options, resource, request):
1277        response = domish.Element((NS_PUBSUB_OWNER, "pubsub"))
1278        default = response.addElement("default")
1279        form = self._formFromConfiguration(resource, options)
1280        default.addChild(form.toElement())
1281        return response
[30]1282
[1]1283
[59]1284    def _toResponse_configureGet(self, options, resource, request):
1285        response = domish.Element((NS_PUBSUB_OWNER, "pubsub"))
1286        configure = response.addElement("configure")
1287        form = self._formFromConfiguration(resource, options)
1288        configure.addChild(form.toElement())
[25]1289
[59]1290        if request.nodeIdentifier:
1291            configure["node"] = request.nodeIdentifier
[1]1292
[59]1293        return response
[1]1294
1295
[59]1296    def _preProcess_configureSet(self, resource, request):
[81]1297        if request.options.formType == 'cancel':
1298            return None
1299        else:
1300            self._checkConfiguration(resource, request.options)
[59]1301            return request
[1]1302
1303
[59]1304    def _toResponse_items(self, result, resource, request):
1305        response = domish.Element((NS_PUBSUB, 'pubsub'))
1306        items = response.addElement('items')
1307        items["node"] = request.nodeIdentifier
[1]1308
[59]1309        for item in result:
1310            items.addChild(item)
[1]1311
[59]1312        return response
[1]1313
1314
[30]1315    def _createNotification(self, eventType, service, nodeIdentifier,
1316                                  subscriber, subscriptions=None):
1317        headers = []
1318
1319        if subscriptions:
1320            for subscription in subscriptions:
1321                if nodeIdentifier != subscription.nodeIdentifier:
1322                    headers.append(('Collection', subscription.nodeIdentifier))
1323
1324        message = domish.Element((None, "message"))
1325        message["from"] = service.full()
1326        message["to"] = subscriber.full()
1327        event = message.addElement((NS_PUBSUB_EVENT, "event"))
1328
1329        element = event.addElement(eventType)
1330        element["node"] = nodeIdentifier
1331
1332        if headers:
1333            message.addChild(shim.Headers(headers))
1334
1335        return message
1336
[93]1337
1338    def _toResponse_affiliationsGet(self, result, resource, request):
1339        response = domish.Element((NS_PUBSUB_OWNER, 'pubsub'))
1340        affiliations = response.addElement('affiliations')
1341
1342        if request.nodeIdentifier:
1343            affiliations['node'] = request.nodeIdentifier
1344
1345        for entity, affiliation in result.iteritems():
1346            item = affiliations.addElement('affiliation')
1347            item['jid'] = entity.full()
1348            item['affiliation'] = affiliation
1349
1350        return response
1351
1352
[59]1353    # public methods
1354
[6]1355    def notifyPublish(self, service, nodeIdentifier, notifications):
[30]1356        for subscriber, subscriptions, items in notifications:
1357            message = self._createNotification('items', service,
1358                                               nodeIdentifier, subscriber,
1359                                               subscriptions)
1360            message.event.items.children = items
[1]1361            self.send(message)
1362
[25]1363
[43]1364    def notifyDelete(self, service, nodeIdentifier, subscribers,
1365                           redirectURI=None):
1366        for subscriber in subscribers:
[30]1367            message = self._createNotification('delete', service,
1368                                               nodeIdentifier,
[43]1369                                               subscriber)
1370            if redirectURI:
1371                redirect = message.event.delete.addElement('redirect')
1372                redirect['uri'] = redirectURI
[15]1373            self.send(message)
1374
[25]1375
[6]1376    def getNodeInfo(self, requestor, service, nodeIdentifier):
[1]1377        return None
1378
[25]1379
[6]1380    def getNodes(self, requestor, service):
[1]1381        return []
1382
[25]1383
[6]1384    def publish(self, requestor, service, nodeIdentifier, items):
[1]1385        raise Unsupported('publish')
1386
[25]1387
[6]1388    def subscribe(self, requestor, service, nodeIdentifier, subscriber):
[1]1389        raise Unsupported('subscribe')
1390
[25]1391
[6]1392    def unsubscribe(self, requestor, service, nodeIdentifier, subscriber):
[1]1393        raise Unsupported('subscribe')
1394
[25]1395
[6]1396    def subscriptions(self, requestor, service):
[1]1397        raise Unsupported('retrieve-subscriptions')
1398
[25]1399
[6]1400    def affiliations(self, requestor, service):
[1]1401        raise Unsupported('retrieve-affiliations')
1402
[25]1403
[6]1404    def create(self, requestor, service, nodeIdentifier):
[1]1405        raise Unsupported('create-nodes')
1406
[25]1407
1408    def getConfigurationOptions(self):
1409        return {}
1410
1411
[43]1412    def getDefaultConfiguration(self, requestor, service, nodeType):
[1]1413        raise Unsupported('retrieve-default')
1414
[25]1415
[6]1416    def getConfiguration(self, requestor, service, nodeIdentifier):
[1]1417        raise Unsupported('config-node')
1418
[25]1419
[6]1420    def setConfiguration(self, requestor, service, nodeIdentifier, options):
[1]1421        raise Unsupported('config-node')
1422
[25]1423
[6]1424    def items(self, requestor, service, nodeIdentifier, maxItems,
1425                    itemIdentifiers):
[1]1426        raise Unsupported('retrieve-items')
1427
[25]1428
[6]1429    def retract(self, requestor, service, nodeIdentifier, itemIdentifiers):
[1]1430        raise Unsupported('retract-items')
1431
[25]1432
[6]1433    def purge(self, requestor, service, nodeIdentifier):
[1]1434        raise Unsupported('purge-nodes')
1435
[25]1436
[6]1437    def delete(self, requestor, service, nodeIdentifier):
[1]1438        raise Unsupported('delete-nodes')
[59]1439
1440
1441
1442class PubSubResource(object):
1443
1444    implements(IPubSubResource)
1445
1446    features = []
1447    discoIdentity = disco.DiscoIdentity('pubsub',
1448                                        'service',
1449                                        'Publish-Subscribe Service')
1450
1451
1452    def locateResource(self, request):
1453        return self
1454
1455
1456    def getInfo(self, requestor, service, nodeIdentifier):
1457        return defer.succeed(None)
1458
1459
1460    def getNodes(self, requestor, service, nodeIdentifier):
1461        return defer.succeed([])
1462
1463
1464    def getConfigurationOptions(self):
1465        return {}
1466
1467
1468    def publish(self, request):
1469        return defer.fail(Unsupported('publish'))
1470
1471
1472    def subscribe(self, request):
1473        return defer.fail(Unsupported('subscribe'))
1474
1475
1476    def unsubscribe(self, request):
1477        return defer.fail(Unsupported('subscribe'))
1478
1479
1480    def subscriptions(self, request):
1481        return defer.fail(Unsupported('retrieve-subscriptions'))
1482
1483
1484    def affiliations(self, request):
1485        return defer.fail(Unsupported('retrieve-affiliations'))
1486
1487
1488    def create(self, request):
1489        return defer.fail(Unsupported('create-nodes'))
1490
1491
1492    def default(self, request):
1493        return defer.fail(Unsupported('retrieve-default'))
1494
1495
1496    def configureGet(self, request):
1497        return defer.fail(Unsupported('config-node'))
1498
1499
1500    def configureSet(self, request):
1501        return defer.fail(Unsupported('config-node'))
1502
1503
1504    def items(self, request):
1505        return defer.fail(Unsupported('retrieve-items'))
1506
1507
1508    def retract(self, request):
1509        return defer.fail(Unsupported('retract-items'))
1510
1511
1512    def purge(self, request):
1513        return defer.fail(Unsupported('purge-nodes'))
1514
1515
1516    def delete(self, request):
1517        return defer.fail(Unsupported('delete-nodes'))
1518
1519
1520    def affiliationsGet(self, request):
1521        return defer.fail(Unsupported('modify-affiliations'))
1522
1523
1524    def affiliationsSet(self, request):
1525        return defer.fail(Unsupported('modify-affiliations'))
1526
1527
1528    def subscriptionsGet(self, request):
1529        return defer.fail(Unsupported('manage-subscriptions'))
1530
1531
1532    def subscriptionsSet(self, request):
1533        return defer.fail(Unsupported('manage-subscriptions'))
Note: See TracBrowser for help on using the repository browser.