Example #1
0
    def populate_payload(self, _container=None):
        """
        Translates the contents of this object into a payload for sending across to the storage entity.
        :return: the populated form
        """

        if _container:
            container = _container
        else:
            container = Form()

        if self.about:
            container.add_field(var=str(RDF.about), value=str(self.about), ftype='text-single')

        if len(self._types):
            container.add_field(var=str(RDF.type), value=self._types, ftype='list-multi')

        for key, value in self._properties.iteritems():
            property_field = container.add_field(var=str(key), value=value, ftype='list-multi')
            type_stanza = FormValidation()
            type_stanza['datatype'] = 'xs:string'
            property_field.append(type_stanza)

        for key, value in self._references.iteritems():
            reference_field = container.add_field(var=str(key), value=value, ftype='list-multi')
            type_stanza = FormValidation()
            type_stanza['datatype'] = 'xs:anyURI'
            reference_field.append(type_stanza)

        for key, value in self._flags.iteritems():
            container.add_field(var=key.var, value=value, ftype=key.field_type)

        return container
Example #2
0
    def __init__(self, container=None):
        """
        Constructor.
        :param container: optional container to populate values from.
        """
        self._results = []

        if not container:
            self._container = Form()
        else:
            self._container = container
            self._unpack_container()
Example #3
0
class ResultCollectionPayload:
    """
    Collection of result payloads.
    """

    def __init__(self, container=None):
        """
        Constructor.
        :param container: optional container to populate values from.
        """
        self._results = []

        if not container:
            self._container = Form()
        else:
            self._container = container
            self._unpack_container()

    def append(self, *args):
        """
        Append a result to the collection.
        :param result:
        :return:
        """
        self._results += args

    def _unpack_container(self):
        """
        Unpack the container into the internal data structures.
        """
        self._results = []

        reported_values = self._container.get_reported()

        for item in self._container.get_items():
            logger.debug('item: %s' % item)
            about = None
            types = None
            flags = dict()
            columns = dict()

            for key, value in item.iteritems():
                if key == str(RDF.about):
                    about = value
                elif key == str(RDF.type):
                    types = value
                else:
                    reported_item = reported_values[key]
                    if reported_item['validate']['datatype']:
                        columns[_ColumnKey(key, reported_item['validate']['datatype'])] = value
                    else:
                        flags[Flag(*(key, reported_item['type'], None))] = value

            result_payload = ResultPayload(about=about, types=types)
            for key, value in flags.iteritems():
                result_payload.add_flag(key, value)

            for key, value in columns.iteritems():
                result_payload.add_column(key.key, value, key.data_type)

            self.append(result_payload)

    def populate_payload(self):
        """
        Populate the data structures into the container for this object, and return it.
        :return: transmittable data structure.
        """
        self._container.clear()

        self._container.add_reported(var=str(RDF.about), ftype='list-multi')
        self._container.add_reported(var=str(RDF.type), ftype='list-multi')

        additional_flags = set()
        additional_columns = set()
        for result in self._results:
            additional_flags.update(result.flags.keys())
            additional_columns.update(result.columns.keys())

        for flag_value in additional_flags:
            self._container.add_reported(var=flag_value.var, ftype=flag_value.field_type)

        for column_value in additional_columns:
            reported = self._container.add_reported(var=column_value.key, ftype='list-multi')
            validation = FormValidation()
            validation['datatype'] = column_value.data_type
            reported.append(validation)

        for result in self._results:
            parameters = {
                str(RDF.about): str(result.about),
                str(RDF.type): result.types
            }

            for key, value in result.flags.iteritems():
                parameters[key.var] = value

            for key, value in result.columns.iteritems():
                parameters[key.key] = value

            self._container.add_item(parameters)

        return self._container

    @property
    def results(self):
        """
        Retrieve the results.
        :return: list of result payloads
        """
        return self._results
Example #4
0
class BaseNode(object):
    nodetype = 'leaf'
    affiliationtypes = ('owner', 'publisher', 'member', 'outcast', 'pending')

    default_config = Form(None, title='Leaf Config Form')
    default_config.addField(
        'FORM_TYPE',
        'hidden',
        value='http://jabber.org/protocol/pubsub#node_config')
    ntype = default_config.addField('pubsub#node_type',
                                    'list-single',
                                    label='Select the node type',
                                    value='leaf')
    ntype.addOption('leaf', 'Leaf')
    default_config.addField('pubsub#title',
                            label='A friendly name for the node')
    default_config.addField('pubsub#deliver_notifications',
                            'boolean',
                            label='Deliver event notifications',
                            value=True)
    default_config.addField('pubsub#deliver_payloads',
                            'boolean',
                            label='Deliver payloads with event notifications',
                            value=True)
    default_config.addField(
        'pubsub#notify_config',
        'boolean',
        label='Notify subscribers when the node configuration changes',
        value=False)
    default_config.addField(
        'pubsub#notify_delete',
        'boolean',
        label='Notify subscribers when the node is deleted',
        value=False)
    default_config.addField(
        'pubsub#notify_retract',
        'boolean',
        label='Notify subscribers when items are removed from the node',
        value=False)
    default_config.addField(
        'pubsub#notify_sub',
        'boolean',
        label='Notify owners about new subscribers and unsubscribes',
        value=False)
    default_config.addField('pubsub#persist_items',
                            'boolean',
                            label='Persist items in storage',
                            value=False)
    default_config.addField('pubsub#max_items',
                            label='Max # of items to persist',
                            value='100')
    default_config.addField('pubsub#expire', label='Expire')
    default_config.addField('pubsub#subscribe',
                            'boolean',
                            label='Whether to allow subscriptions',
                            value=True)
    default_config.addField('pubsub#collection',
                            'text-multi',
                            label="This node in collections")
    default_config.addField('sleek#saveonchange',
                            'boolean',
                            label='Save on every change',
                            value=False)
    default_config.addField('sleek#dupesubscriptions',
                            'boolean',
                            label='Allow dupe subscriptions',
                            value=True)
    model = default_config.addField('pubsub#access_model',
                                    'list-single',
                                    label='Specify the subscriber model',
                                    value='open')
    #model.addOption('authorize', 'Authorize') # not yet implemented
    model.addOption('open', 'Open')
    model.addOption('whitelist', 'whitelist')
    model = default_config.addField('pubsub#publish_model',
                                    'list-single',
                                    label='Specify the publisher model',
                                    value='publishers')
    model.addOption('publishers', 'Publishers')
    model.addOption('subscribers', 'Subscribers')
    model.addOption('open', 'Open')
    model = default_config.addField('pubsub#send_last_published_item',
                                    'list-single',
                                    label='Send last published item',
                                    value='never')
    model.addOption('never', 'Never')
    model.addOption('on_sub', 'On Subscription')
    model.addOption('on_sub_and_presence', 'On Subscription And Presence')
    default_config.addField(
        'pubsub#presence_based_delivery',
        'boolean',
        label='Deliver notification only to available users',
        value=False)
    del ntype
    del model

    item_class = Item
    itemevent_class = ItemEvent

    def __init__(self,
                 pubsub,
                 db,
                 name,
                 config=None,
                 owner=None,
                 fresh=False,
                 use_db=True):
        self.new_owner = owner
        self.use_db = use_db
        self.fresh = fresh
        self.pubsub = pubsub
        self.xmpp = self.pubsub.xmpp
        self.db = db
        self.name = name
        self.collections = []
        self.config = config or copy.copy(self.default_config)
        self.subscription_form = {}
        self.publish_form = {}
        self.items = {}
        self.itemorder = []
        self.synch = True
        self.state = ''
        #self.affiliations = {'owner': [], 'publisher': [], 'member': [], 'outcast': [], 'pending': []}
        self.affiliations = {}
        for afftype in self.affiliationtypes:
            self.affiliations[afftype] = []
        self.subscriptions = {}
        self.subscriptionsbyjid = {}
        if self.new_owner is not None:
            self.affiliations['owner'].append(self.new_owner)
        self.dbLoad()
        self.lastsaved = time.time()
        if self.pubsub.config['settings'][
                'node_creation'] == 'createonsubscribe':
            self.use_db = False

        self.updates_per_second = 0.0
        self.recent_updates = 0
        self.recent_update_time = time.time()

    def dbLoad(self):
        if not self.use_db:
            return
        if not self.fresh:
            self.affiliations = self.db.getAffiliations(self.name)
            self.items = self.db.getItems(self.name)
            self.config = pickle.loads(self.db.getNodeConfig(self.name))
            parentset = self._checkconfigcollections(self.config, False)
            if not parentset:
                logging.warning("Was not able to set all parents in %s" %
                                self.name)
            subs = self.db.getSubscriptions(self.name)
            for jid, subid, config in subs:
                self.subscriptions[subid] = Subscription(
                    self, jid, subid, config)
                self.subscriptionsbyjid[jid] = self.subscriptions[subid]
        else:
            self.db.createNode(self.name, self.config, self.affiliations,
                               self.items)

    def dbDump(self, save=False):
        if not self.use_db:
            return
        if save:
            self.db.synch(self.name,
                          pickle.dumps(self.config),
                          self.affiliations,
                          self.items,
                          subscriptions=self.subscriptions)
        else:
            self.db._synch(self.name,
                           pickle.dumps(self.config),
                           self.affiliations,
                           self.items,
                           subscriptions=self.subscriptions)
        self.lastsaved = time.time()

    def save(self):
        if not self.use_db:
            return
        logging.info("Saving %s" % self.name)
        #self.dbDump(True)
        self.db._synch(self.name,
                       pickle.dumps(self.config),
                       self.affiliations,
                       self.items,
                       subscriptions=self.subscriptions,
                       newdb=True)

    def discoverItems(self):
        pass

    def getSubscriptions(self):
        return self.subscriptions

    def getAffiliations(self):
        return self.affiliations

    def notifyItemState(self, xml, item_id=None, who=None):
        return
        msg = self.xmpp.Message()
        msg['psstate_event']['psstate']['node'] = self.name
        msg['psstate_event']['psstate']['item'] = item_id
        msg['psstate_event']['psstate']['payload'] = xml
        for step in self.eachSubscriber():
            for jid, mto in step:
                msg['from'] = mto
                msg['to'] = jid
                msg.send()
        for affiliation in self.affiliations:
            if affiliation == 'member':
                continue
            for barejid in self.affiliations[affiliation]:
                resources = self.xmpp.roster.get(
                    barejid, {'presence': {}})['presence'].keys()
                if resources:
                    for resource in resources:
                        msg['from'] = self.xmpp.boundjid
                        msg['to'] = "%s/%s" % (barejid, resource)
                        msg.send()
                else:
                    msg['from'] = self.xmpp.boundjid
                    msg['to'] = barejid
                    msg.send()

    def subscribe(self, jid, who=None, config=None, to=None):
        if ((who is None or self.xmpp.getjidbare(who)
             in self.affiliations['owner'] or who.startswith(jid)) and
            (self.config['pubsub#access_model'] == 'open' or
             (self.config['pubsub#access_model'] == 'whitelist'
              and jid in self.affiliations['member']) or
             (self.xmpp.getjidbare(who) in self.affiliations['owner']))):
            if not self.config[
                    'sleek#dupesubscriptions'] and jid in self.subscriptionsbyjid:
                return False
            subid = uuid.uuid4().hex
            if config is not None:
                config = ET.tostring(config.getXML('submit'))
            self.subscriptions[subid] = Subscription(self, jid, subid, config,
                                                     to)
            self.subscriptionsbyjid[jid] = self.subscriptions[subid]
            if self.config['sleek#saveonchange'] and self.use_db:
                self.db.addSubscription(self.name, jid, subid, config, to)
            if self.config['pubsub#send_last_published_item'] in (
                    'on_sub', 'on_sub_and_presence'):
                if len(self.itemorder) > 0:
                    event = ItemEvent(self.name, self.items[self.itemorder[0]])
                    if len(self.itemorder) > 1:
                        for item in self.itemorder[1:]:
                            event.addItem(self.items[item])
                    self.notifyItem(event, jid)
            return subid
        else:
            return False
        #TODO modify affiliation

    def unsubscribe(self, jid, who=None, subid=None):
        if subid is None:
            try:
                subid = self.subscriptionsbyjid[jid].getid()
            except KeyError:
                return False
        if self.config['sleek#saveonchange'] and self.use_db:
            self.db.deleteSubscription(self.name, jid, subid)
        try:
            del self.subscriptions[subid]
            if self.subscriptionsbyjid[jid].getid() == subid:
                del self.subscriptionsbyjid[jid]
        except IndexError():
            return False
        #TODO add error cases
        #TODO add ACL
        return True

    def getSubscriptionOptions(self):
        pass

    def setSubscriptionOptions(self):
        pass

    def getItems(self, max=0):
        pass

    def getLastItem(self, node):
        pass

    def eachSubscriber(self, step=1, filterjid=None):
        "Generator for subscribers."
        if step < 1:
            raise ValueError
        subscriptions = self.subscriptions.keys()
        result = []
        idx = 0  # would enumerate, but num of results isn't necessary the same as len(subscriptions)
        for subid in subscriptions:
            subscriber = self.subscriptions[subid]
            jid = subscriber.getjid()
            mto = subscriber.getto()
            if not (filterjid is not None and (filterjid != jid)):
                if '/' in jid or not self.config.get(
                        'pubsub#presence_based_delivery', False):
                    result.append((jid, mto))
                    if idx % step == 0:
                        yield result
                        result = []
                    idx += 1
                else:
                    resources = self.xmpp.roster.get(
                        jid, {'presence': {}})['presence'].keys()
                    random.shuffle(resources)
                    for resource in resources:
                        result.append(("%s/%s" % (jid, resource), mto))
                        if idx % step == 0:
                            yield result
                            result = []
                        idx += 1

    def publish(self, item, item_id=None, options=None, who=None):
        self.recent_updates += 1
        spent = time.time() - self.recent_update_time
        if spent >= 10.0:
            self.updates_per_second = float(self.recent_updates) / spent
            self.recent_update_time = time.time()
            self.recent_updates = 1
        if not item_id:
            item_id = uuid.uuid4().hex
        self.xmpp.schedule("%s::%s::publish" % (self.name, item_id), 0,
                           self._publish, (item, item_id, options, who))
        return item_id

    def _publish(self, item, item_id=None, options=None, who=None):
        if item.tag == '{http://jabber.org/protocol/pubsub}item':
            payload = item.getchildren()[0]
        else:
            payload = item
        item_inst = self.item_class(self, item_id, who, payload, options)
        if self.config.get('pubsub#persist_items', False):
            if self.config['sleek#saveonchange'] and self.use_db:
                self.db.setItem(self.name, item_id, payload)
            self.items[item_id] = item_inst
            if item_id not in self.itemorder:
                self.itemorder.append(item_id)
            else:
                self.itemorder.append(
                    self.itemorder.pop(self.itemorder.index(item_id)))
        event = self.itemevent_class(self.name, item_inst)
        self.notifyItem(event)
        max_items = int(self.config.get('pubsub#max_items', 0))
        if max_items != 0 and len(self.itemorder) > max_items:
            self.deleteItem(self.itemorder[0])

    def deleteItem(self, id):
        if id in self.items:
            item = self.items[id]
            del self.items[id]
            self.itemorder.pop(self.itemorder.index(id))
            self.notifyDelete(ItemEvent(self.name, item))
        #TODO: DB

    def _checkconfigcollections(self, config, reconfigure=True):
        collections = []
        passed = True
        nodes = config.get('pubsub#collection', [])
        if type(nodes) != type([]):
            nodes = [nodes]
        for node in nodes:
            if node not in self.pubsub.nodes:
                passed = False
            else:
                collections.append(node)
        if not reconfigure or passed:
            self.collections = collections
        return passed

    def create(self, config=None):
        pass

    def getConfig(self, default=False):
        return self.config

    def configure(self, config):
        if not self._checkconfigcollections(config):
            raise XMPPError()  #TODO make this the right error
        self.config.update(config)
        # we do this regardless of cache settings
        if self.use_db:
            self.db.synch(self.name, config=pickle.dumps(self.config))

    def setState(self, state, who):
        pass

    def setItemState(self, item_id, state, who=None):
        if item_id in self.items:
            return self.items[item_id].setState(state, who)
        return False

    def _saveState(self, xml):
        pass

    def _saveItemState(self, node, xml):
        pass

    def purgeNodeItems(self):
        pass

    def approvePendingSubscription(self, jid):
        pass

    def modifySubscriptions(self, jids={}):
        pass

    def modifyAffiliations(self, affiliations={}, who=None):
        if who is not None and who not in self.affiliations['owner']:
            return False
        for key in affiliations:
            if key not in self.affiliationtypes:
                return False
        self.affiliations.update(affiliations)
        if self.config['sleek#saveonchange'] and self.use_db:
            self.db.synch(self.name, affiliations=self.affiliations)
        return True

    def getAffiliations(self, who=None):
        if who is not None and who not in self.affiliations['owner']:
            return False
        return self.affiliations

    def notifyItem(self, event, filterjid=None):
        if event.hasNode(self.name):
            return False
        event.addNode(self.name)
        jid = ''
        msg = self.xmpp.Message()
        msg['to'] = jid
        msg['from'] = self.xmpp.boundjid
        xevent = ET.Element('{http://jabber.org/protocol/pubsub#event}event')
        items = ET.Element('items', {'node': event.originalnode})
        for itemi in event.item:
            item_id = itemi.name
            payload = itemi.payload
            item = ET.Element('item', {'id': item_id})
            item.append(payload)
            items.append(item)
        xevent.append(items)
        if payload.tag == '{jabber:client}body':
            msg['body'] = payload.text
            msg['type'] = 'chat'
        else:
            msg.append(xevent)
        for toset in self.eachSubscriber(filterjid=filterjid):
            jid, mto = toset[0]
            if not event.hasJid(jid):
                event.addJid(jid)
                msg['to'] = jid
                msg['from'] = mto or self.xmpp.boundjid
                self.xmpp.send(msg)
        for parent in self.collections:
            if parent in self.pubsub.nodes:
                self.pubsub.nodes[parent].notifyItem(event, jid)

    def notifyConfig(self):
        pass

    def notifyDelete(self, event):
        if event.hasNode(self.name):
            return False
        event.addNode(self.name)
        jid = ''
        msg = self.xmpp.Message()
        msg['to'] = jid
        msg['from'] = self.xmpp.boundjid

        xevent = ET.Element('{http://jabber.org/protocol/pubsub#event}event')
        items = ET.Element('items', {'node': event.originalnode})
        item = ET.Element('retract', {'id': event.item[0].name})
        #item.append(payload)
        items.append(item)
        xevent.append(items)
        msg.append(xevent)
        for toset in self.eachSubscriber():
            jid, mto = toset[0]
            if not event.hasJid(jid):
                event.addJid(jid)
                msg['to'] = jid
                print "WHAT THE HELL IS", mto, type(mto)
                msg['from'] = mto or self.xmpp.boundjid
                self.xmpp.send(msg)
        for parent in self.collections:
            if parent in self.pubsub.nodes:
                self.pubsub.nodes[parent].notifyDelete(event)

    def delete(self):
        for sub in self.subscriptions.keys():
            del self.subscriptions[sub]
        for sub in self.subscriptionsbyjid.keys():
            del self.subscriptionsbyjid[sub]
        for item in self.items.keys():
            del self.items[item]
        for collection in self.collections:
            self.collections.pop(self.collections.index(collection))