PubSub and attaching LeafNode to CollectionNode

I am trying to get a LeafNode attached to a CollectionNode using PubSubManager, and I’m a little confused about the syntax of doing this.

Can someone confirm/correct the assumptions I’m making?

First, I make a connection to Openfire server with an “manager” account I created. This connection creates a CollectionNode that’s open for everyone:

public static boolean createCollectionNode(){
        PubSubManager m_manager = new PubSubManager(m_xmppConnection);
        CollectionNode node = null;         ConfigureForm nodeConfig = new ConfigureForm(FormType.submit);
        nodeConfig.setPersistentItems(false);
        nodeConfig.setDeliverPayloads(true);
        nodeConfig.setAccessModel(AccessModel.open);
        nodeConfig.setPublishModel(PublishModel.open);
        nodeConfig.setNodeType(NodeType.collection);         try {
            node = (CollectionNode) m_manager.getNode("clients");         } catch (XMPPException e) {}         try {
            if (null == node){
                node = (CollectionNode) m_manager.createNode("clients", nodeConfig);
            }
        } catch (XMPPException e) {
            System.out.println("This is bad!!  Could not create client collection node: " + e.getLocalizedMessage());
        }
        return true;
    }

Questions:

  1. Seems to create a “CollectionNode”, is this correct?
  2. How about the “/” separator at the root level? Do I need to include the root level “/” to properly create the node at the root level?
  3. Proper idiom for testing that the node already exists?

After the creation of the CollectionNode, I want all of my clients to create their individual nodes under the “clients” node. Here’s how I’m doing that…

/*
     *                     CREATE THE PUBLISHED NODE
     *      */
    public boolean createNode(){
        ConfigureForm nodeConfigForm = new ConfigureForm(FormType.submit);
        nodeConfigForm.setPersistentItems(false);
        nodeConfigForm.setDeliverPayloads(false);
        nodeConfigForm.setAccessModel(AccessModel.open);
        nodeConfigForm.setPublishModel(PublishModel.open);
        nodeConfigForm.setCollection("clients");         m_manager = new PubSubManager(m_xmppConnection);         try {
            m_node = (LeafNode) m_manager.getNode(m_nodeId);
        } catch (XMPPException e) {}         try {
            if (null == m_node){
                m_node = (LeafNode) m_manager.createNode(m_nodeId, nodeConfigForm);
            }
        } catch (XMPPException e){
            System.out.println("Exception occured while trying to create node: " + e.getLocalizedMessage());
            e.printStackTrace();         }         if (m_listenSelf)
            m_node.addItemEventListener(m_pubsubListener);         return true;
    }

Questions:

  1. Am I attaching the node correctly? Javadocs don’t seem to be too clear about the usages of the ConfigForm
  2. Do I need to include a separator in the node name here? I.e. “m_manager.createNode(”/clients/clientNode0", nodeConfigForm) or should I just create the node with it’s short name because I’ve already specified the parent collection like "m_manager.createNode(“clientNode0”, nodeConfigForm)

I’d love to talk with someone who’s gotten this to work correctly. I’d love to see some working code samples.

Thanks

DD

This has never been officially tested since the API was not completed with respect to Collection nodes. That being said, most of the configuration should work, as it is defined purely within the XEP-0060 spec.

Node ids do not require any ‘/’ characters. The id is whatever you actually enter.

For checking for a node’s existence, you can either get the specific node (manager.getNode()) or check the results of *manager.discoverNodes(parentId). *I will reiterate though that this has not been tested with collection nodes. I don’t have the code in front of me, so I am not sure about how well the getNode() will work, although discoverNodes() should.

NOTE: ejabberd does have (or had) some constraints with naming nodes that requires an hierarchical path for an id. I do not know if this is still the case or not. The API is written to the spec, which does not have any such constraint.

1 Like

**UPDATE x2: **

Yes Supported… See code snippet at bottom.

You must use the correct subscription configuration to correctly set the node depth, otherwise nothing…


Thanks for the reply,

I did some additional testing yesterday but was still not able to get subscriptions to a CollectionNode to respond when a LeafNode update occurs. I’m not sure if there are “ordering” issues around the creation and subscription process, but would really like to get CollectionNodes and PubSub working (at least as far as XEP-060)

Here’s a basic outline of how I approached with code samples of key points:

1) Create a “manager” account in main() which opens a connection to Openfire and creates a CollectionNode named “TestCollection”.

/*
     * Creates the collection node for everyone in the system, adds our own listeners to the node and
     * subscribes the main function to updadtes from that node.
     */
    public static boolean createCollectionNode(){
        PubSubManager m_manager = new PubSubManager(m_xmppConnection, SimpleConstants.SERVER_SERVICE);
        CollectionNode node = null;
        String subscriber = SimpleConstants.MANAGER_NAME + "@" + m_xmppConnection.getServiceName();         ConfigureForm nodeConfig = new ConfigureForm(FormType.submit);
        nodeConfig.setPersistentItems(false);
        nodeConfig.setDeliverPayloads(true);
        nodeConfig.setAccessModel(AccessModel.open);
        nodeConfig.setPublishModel(PublishModel.open);
        nodeConfig.setNodeType(NodeType.collection);         try {
            node = (CollectionNode) m_manager.getNode(SimpleConstants.COLLECTION_NODE_NAME);         } catch (XMPPException e) {}         try {
            if (null == node){
                node = (CollectionNode) m_manager.createNode(SimpleConstants.COLLECTION_NODE_NAME, nodeConfig);
            }
            else {
                node.sendConfigurationForm(nodeConfig);
            }             //  Remove any existing subscriptions...
            List<Subscription> subscriptions = node.getSubscriptions();
            for (Subscription s : subscriptions){
                if (s.getJid().equalsIgnoreCase(subscriber)){
                    node.unsubscribe(subscriber);
                }
            }             node.addConfigurationListener(new MyNodeConfigListener());
            node.addItemEventListener(new MyEventListener());
            SubscribeForm subscriptionForm = new SubscribeForm(FormType.submit);
            subscriptionForm.setDeliverOn(true);
            subscriptionForm.setDigestFrequency(5000);
            subscriptionForm.setDigestOn(true);
            subscriptionForm.setIncludeBody(true);
            node.subscribe(subscriber, subscriptionForm);         } catch (XMPPException e) {
            System.out.println("This is bad!!  Could not create client collection node: " + e.getLocalizedMessage());
        }                return true;
    }

2) Using the same connection in main(), I create test accounts for 5 publishers. These will be scheduled through an executor pool to post random time updates.

/*
     *                     CREATE THE PUBLISHED NODE
     *      */
    public boolean createNode(){
        ConfigureForm nodeConfigForm = new ConfigureForm(FormType.submit);
        nodeConfigForm.setPersistentItems(false);
        nodeConfigForm.setDeliverPayloads(true);
        nodeConfigForm.setAccessModel(AccessModel.open);
        nodeConfigForm.setPublishModel(PublishModel.open);
        nodeConfigForm.setCollection(SimpleConstants.COLLECTION_NODE_NAME);         m_manager = new PubSubManager(m_xmppConnection, SimpleConstants.SERVER_SERVICE);         try {
            m_node = (LeafNode) m_manager.getNode(m_nodeId);
        } catch (XMPPException e) {}         try {
            if (null == m_node){
                m_node = (LeafNode) m_manager.createNode(m_nodeId, nodeConfigForm);
            }
            else {
                m_node.sendConfigurationForm(nodeConfigForm);
            }
        } catch (XMPPException e){
            System.out.println("Exception occured while trying to create node: " + e.getLocalizedMessage());
            e.printStackTrace();         }         if (m_listenSelf)
            m_node.addItemEventListener(m_pubsubListener);         return true;
    }     /*
     *                     PUBLISH TIME....
     */
    public void          publishTime(){
        System.out.print('.');
        SimplePayload payload = new SimplePayload("time", SimpleConstants.SERVER_SERVICE,
                "<time xmlns='pubsub:testnode1:time'>" +
                        "<title>" + String.valueOf(System.currentTimeMillis()) + "</title>" +
                "</time>");         PayloadItem payloadItem = new PayloadItem("time", m_nodeId, payload);
        m_node.publish(payloadItem);     }

3) After the creation of the test accounts, I do a little sanity checking. I’m observing that each of the different XMPPConnection objects and/or PubSubManagers reports a different set of subscribed accounts. With the snippet below, only the subscriptions created through “manager” are listed, even though a separate “receiver” is created and subscribed elsewhere. I check the subscription status within the Postgresql database, and the CollectionNode shows both of the two subscribers I have configured, the “manager” and the “receiver” are properly listed.

//  SANITY CHECK!!!!!
            System.out.println("Completed initialization o clients, begin sanity check...");
            PubSubManager manager = new PubSubManager(m_xmppConnection, SimpleConstants.SERVER_SERVICE);
            try {
                DiscoverItems items = manager.discoverNodes(SimpleConstants.COLLECTION_NODE_NAME);
                Iterator<org.jivesoftware.smackx.packet.DiscoverItems.Item> itemItor = items.getItems();
                while (itemItor.hasNext()){
                    org.jivesoftware.smackx.packet.DiscoverItems.Item i = itemItor.next();
                    System.out.println("Child of node: "+ SimpleConstants.COLLECTION_NODE_NAME + " found :" + i.getNode());
                }
            } catch (XMPPException e) {}             //  MORE SANITY CHECK!  CANT HAVE TOO MUCH SANITY CHECKING...
            System.out.println("More sanity checking...");
            try {
                List<Subscription> subscriptions = manager.getSubscriptions();                 Iterator<Subscription> subItor = subscriptions.iterator();
                while (subItor.hasNext()){
                    Subscription s = subItor.next();
                    System.out.println("Subscription to node: " + s.getNode() + " by " + s.getJid());
                }
            } catch (XMPPException e1) {}

Here’s what the resulting subscription check looks like for the “receiver” node:

<iq id="xDwvy-89" to="testreceiver@locator.adspore.com/Smack" from="pubsub.locator.adspore.com" type="result">
  <pubsub xmlns="http://jabber.org/protocol/pubsub">
    <subscriptions>
      <subscription jid="testreceiver@locator.adspore.com" node="TestCollection" subid="7X5JhTW1S3NpY3X0lJteEfxXDnIsoC17TV85i15u" subscription="subscribed"/>
    </subscriptions>
  </pubsub>
</iq>

And here’s what the subscription check looks like for the “manager” node:

<iq id="xDwvy-11" to="test_account_mgr@locator.adspore.com/Smack" from="pubsub.locator.adspore.com" type="result">
  <pubsub xmlns="http://jabber.org/protocol/pubsub">
    <subscriptions node="TestCollection">
      <subscription jid="test_account_mgr@locator.adspore.com" subid="j7pU9031Hh88MLCG2NCR4QiK7CQRMAJR89HXnKrj" subscription="subscribed"/>
    </subscriptions>
  </pubsub>
</iq>

Here’s what the output of the code above looks like when it’s run:

SimpleListener.cleanupAccount()...  Has subscription:testreceiver@locator.adspore.com to Node:TestCollection
SimpleListener... Checking subscriptions on local PubSubManager...
Subscription to node: TestCollection by testreceiver@locator.adspore.com
Completed initialization o clients, begin sanity check...
Child of node: TestCollection found :testClient4
Child of node: TestCollection found :testClient3
Child of node: TestCollection found :testClient1
Child of node: TestCollection found :testClient2
Child of node: TestCollection found :testClient0
More sanity checking...
Subscription to node: TestCollection by test_account_mgr@locator.adspore.com
Completed scheduling

**4) **Begin the scheduling and observe the results… The “publish” method is shown in step #2 above, simply putting up the current time.

//  Schedule all of the clients...
            itor = clients.iterator();
            Random random = new Random();
            while (itor.hasNext()){
                SimpleBroadcaster client = itor.next();
                long initial = 2L;
                long interval = 1L + ((long)(random.nextInt(4)));
                m_executor.scheduleAtFixedRate(new ClientExecutor(client), initial, interval, TimeUnit.SECONDS);
            }
            System.out.println("Completed scheduling");             //  Wait to quit....
            try {
                Thread.sleep(1000 * 60 * 2);
            } catch (InterruptedException e) {}         }
    }     static class ClientExecutor implements Runnable {
        SimpleBroadcaster          m_broadcaster;         public ClientExecutor(SimpleBroadcaster broadcaster){
            m_broadcaster = broadcaster;
        }         public void run(){
            m_broadcaster.publishTime();
        }
    }

ANSWER:

Must use the correct form configuration to set the depth and notification type. Disregard the “all” subscription type, I modified my Openfire instance to properly handle both node additions/deletions as well as publishing events.

// Get the node
                          Node node = mgr.getNode("gamedata/locations");
                                                    SubscribeForm form = new SubscribeForm(new ConfigureForm(FormType.submit));
                                                    FormField field = new FormField("FORM_TYPE");
                          field.setType(FormField.TYPE_HIDDEN);
                          field.addValue("http://jabber.org/protocol/pubsub#subscribe_options");
                          form.addField(field);
                                                    field = new FormField("pubsub#subscription_type");
                          field.setType(FormField.TYPE_TEXT_SINGLE);
                          field.addValue("all");
                          form.addField(field);
                                                    field = new FormField("pubsub#subscription_depth");
                          field.setType(FormField.TYPE_TEXT_SINGLE);
                          field.addValue("all");
                          form.addField(field);
                                                    node.addItemEventListener(new GeoEventListener<Item>());
                          node.subscribe(connection.getUser(), form);

Thanks again,

DD