PubSubManager.java

  1. /**
  2.  *
  3.  * Copyright the original author or authors
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.pubsub;

  18. import java.util.Collections;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.concurrent.ConcurrentHashMap;

  22. import org.jivesoftware.smack.SmackException.NoResponseException;
  23. import org.jivesoftware.smack.SmackException.NotConnectedException;
  24. import org.jivesoftware.smack.XMPPConnection;
  25. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  26. import org.jivesoftware.smack.packet.EmptyResultIQ;
  27. import org.jivesoftware.smack.packet.IQ;
  28. import org.jivesoftware.smack.packet.IQ.Type;
  29. import org.jivesoftware.smack.packet.Stanza;
  30. import org.jivesoftware.smack.packet.ExtensionElement;
  31. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  32. import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
  33. import org.jivesoftware.smackx.disco.packet.DiscoverItems;
  34. import org.jivesoftware.smackx.pubsub.packet.PubSub;
  35. import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
  36. import org.jivesoftware.smackx.pubsub.util.NodeUtils;
  37. import org.jivesoftware.smackx.xdata.Form;
  38. import org.jivesoftware.smackx.xdata.FormField;
  39. import org.jxmpp.jid.DomainBareJid;
  40. import org.jxmpp.jid.Jid;
  41. import org.jxmpp.jid.impl.JidCreate;
  42. import org.jxmpp.stringprep.XmppStringprepException;

  43. /**
  44.  * This is the starting point for access to the pubsub service.  It
  45.  * will provide access to general information about the service, as
  46.  * well as create or retrieve pubsub {@link LeafNode} instances.  These
  47.  * instances provide the bulk of the functionality as defined in the
  48.  * pubsub specification <a href="http://xmpp.org/extensions/xep-0060.html">XEP-0060</a>.
  49.  *
  50.  * @author Robin Collier
  51.  */
  52. final public class PubSubManager
  53. {
  54.     private XMPPConnection con;
  55.     private DomainBareJid to;
  56.     private Map<String, Node> nodeMap = new ConcurrentHashMap<String, Node>();
  57.    
  58.     /**
  59.      * Create a pubsub manager associated to the specified connection.  Defaults the service
  60.      * name to <i>pubsub</i>
  61.      *
  62.      * @param connection The XMPP connection
  63.      * @throws XmppStringprepException
  64.      */
  65.     public PubSubManager(XMPPConnection connection) throws XmppStringprepException
  66.     {
  67.         con = connection;
  68.         to = JidCreate.domainBareFrom("pubsub." + connection.getServiceName());
  69.     }
  70.    
  71.     /**
  72.      * Create a pubsub manager associated to the specified connection where
  73.      * the pubsub requests require a specific to address for packets.
  74.      *
  75.      * @param connection The XMPP connection
  76.      * @param toAddress The pubsub specific to address (required for some servers)
  77.      */
  78.     public PubSubManager(XMPPConnection connection, DomainBareJid toAddress)
  79.     {
  80.         con = connection;
  81.         to = toAddress;
  82.     }
  83.    
  84.     /**
  85.      * Creates an instant node, if supported.
  86.      *
  87.      * @return The node that was created
  88.      * @throws XMPPErrorException
  89.      * @throws NoResponseException
  90.      * @throws NotConnectedException
  91.      * @throws InterruptedException
  92.      */
  93.     public LeafNode createNode() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  94.     {
  95.         PubSub reply = sendPubsubPacket(Type.set, new NodeExtension(PubSubElementType.CREATE), null);
  96.         NodeExtension elem = reply.getExtension("create", PubSubNamespace.BASIC.getXmlns());
  97.        
  98.         LeafNode newNode = new LeafNode(con, elem.getNode());
  99.         newNode.setTo(to);
  100.         nodeMap.put(newNode.getId(), newNode);
  101.        
  102.         return newNode;
  103.     }
  104.    
  105.     /**
  106.      * Creates a node with default configuration.
  107.      *
  108.      * @param id The id of the node, which must be unique within the
  109.      * pubsub service
  110.      * @return The node that was created
  111.      * @throws XMPPErrorException
  112.      * @throws NoResponseException
  113.      * @throws NotConnectedException
  114.      * @throws InterruptedException
  115.      */
  116.     public LeafNode createNode(String id) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  117.     {
  118.         return (LeafNode)createNode(id, null);
  119.     }
  120.    
  121.     /**
  122.      * Creates a node with specified configuration.
  123.      *
  124.      * Note: This is the only way to create a collection node.
  125.      *
  126.      * @param name The name of the node, which must be unique within the
  127.      * pubsub service
  128.      * @param config The configuration for the node
  129.      * @return The node that was created
  130.      * @throws XMPPErrorException
  131.      * @throws NoResponseException
  132.      * @throws NotConnectedException
  133.      * @throws InterruptedException
  134.      */
  135.     public Node createNode(String name, Form config) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  136.     {
  137.         PubSub request = PubSub.createPubsubPacket(to, Type.set, new NodeExtension(PubSubElementType.CREATE, name), null);
  138.         boolean isLeafNode = true;
  139.        
  140.         if (config != null)
  141.         {
  142.             request.addExtension(new FormNode(FormNodeType.CONFIGURE, config));
  143.             FormField nodeTypeField = config.getField(ConfigureNodeFields.node_type.getFieldName());
  144.            
  145.             if (nodeTypeField != null)
  146.                 isLeafNode = nodeTypeField.getValues().get(0).equals(NodeType.leaf.toString());
  147.         }

  148.         // Errors will cause exceptions in getReply, so it only returns
  149.         // on success.
  150.         sendPubsubPacket(con, request);
  151.         Node newNode = isLeafNode ? new LeafNode(con, name) : new CollectionNode(con, name);
  152.         newNode.setTo(to);
  153.         nodeMap.put(newNode.getId(), newNode);
  154.        
  155.         return newNode;
  156.     }

  157.     /**
  158.      * Retrieves the requested node, if it exists.  It will throw an
  159.      * exception if it does not.
  160.      *
  161.      * @param id - The unique id of the node
  162.      * @return the node
  163.      * @throws XMPPErrorException The node does not exist
  164.      * @throws NoResponseException if there was no response from the server.
  165.      * @throws NotConnectedException
  166.      * @throws InterruptedException
  167.      */
  168.     @SuppressWarnings("unchecked")
  169.     public <T extends Node> T getNode(String id) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  170.     {
  171.         Node node = nodeMap.get(id);
  172.        
  173.         if (node == null)
  174.         {
  175.             DiscoverInfo info = new DiscoverInfo();
  176.             info.setTo(to);
  177.             info.setNode(id);
  178.            
  179.             DiscoverInfo infoReply = (DiscoverInfo) con.createPacketCollectorAndSend(info).nextResultOrThrow();

  180.             if (infoReply.hasIdentity(PubSub.ELEMENT, "leaf")) {
  181.                 node = new LeafNode(con, id);
  182.             }
  183.             else if (infoReply.hasIdentity(PubSub.ELEMENT, "collection")) {
  184.                 node = new CollectionNode(con, id);
  185.             }
  186.             else {
  187.                 // XEP-60 5.3 states that
  188.                 // "The 'disco#info' result MUST include an identity with a category of 'pubsub' and a type of either 'leaf' or 'collection'."
  189.                 // If this is not the case, then we are dealing with an PubSub implementation that doesn't follow the specification.
  190.                 throw new AssertionError(
  191.                                 "PubSub service '"
  192.                                                 + to
  193.                                                 + "' returned disco info result for node '"
  194.                                                 + id
  195.                                                 + "', but it did not contain an Identity of type 'leaf' or 'collection' (and category 'pubsub'), which is not allowed according to XEP-60 5.3.");
  196.             }
  197.             node.setTo(to);
  198.             nodeMap.put(id, node);
  199.         }
  200.         return (T) node;
  201.     }
  202.    
  203.     /**
  204.      * Get all the nodes that currently exist as a child of the specified
  205.      * collection node.  If the service does not support collection nodes
  206.      * then all nodes will be returned.
  207.      *
  208.      * To retrieve contents of the root collection node (if it exists),
  209.      * or there is no root collection node, pass null as the nodeId.
  210.      *
  211.      * @param nodeId - The id of the collection node for which the child
  212.      * nodes will be returned.  
  213.      * @return {@link DiscoverItems} representing the existing nodes
  214.      * @throws XMPPErrorException
  215.      * @throws NoResponseException if there was no response from the server.
  216.      * @throws NotConnectedException
  217.      * @throws InterruptedException
  218.      */
  219.     public DiscoverItems discoverNodes(String nodeId) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  220.     {
  221.         DiscoverItems items = new DiscoverItems();
  222.        
  223.         if (nodeId != null)
  224.             items.setNode(nodeId);
  225.         items.setTo(to);
  226.         DiscoverItems nodeItems = (DiscoverItems) con.createPacketCollectorAndSend(items).nextResultOrThrow();
  227.         return nodeItems;
  228.     }
  229.    
  230.     /**
  231.      * Gets the subscriptions on the root node.
  232.      *
  233.      * @return List of exceptions
  234.      * @throws XMPPErrorException
  235.      * @throws NoResponseException
  236.      * @throws NotConnectedException
  237.      * @throws InterruptedException
  238.      */
  239.     public List<Subscription> getSubscriptions() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  240.     {
  241.         Stanza reply = sendPubsubPacket(Type.get, new NodeExtension(PubSubElementType.SUBSCRIPTIONS), null);
  242.         SubscriptionsExtension subElem = reply.getExtension(PubSubElementType.SUBSCRIPTIONS.getElementName(), PubSubElementType.SUBSCRIPTIONS.getNamespace().getXmlns());
  243.         return subElem.getSubscriptions();
  244.     }
  245.    
  246.     /**
  247.      * Gets the affiliations on the root node.
  248.      *
  249.      * @return List of affiliations
  250.      * @throws XMPPErrorException
  251.      * @throws NoResponseException
  252.      * @throws NotConnectedException
  253.      * @throws InterruptedException
  254.      *
  255.      */
  256.     public List<Affiliation> getAffiliations() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  257.     {
  258.         PubSub reply = sendPubsubPacket(Type.get, new NodeExtension(PubSubElementType.AFFILIATIONS), null);
  259.         AffiliationsExtension listElem = reply.getExtension(PubSubElementType.AFFILIATIONS);
  260.         return listElem.getAffiliations();
  261.     }

  262.     /**
  263.      * Delete the specified node
  264.      *
  265.      * @param nodeId
  266.      * @throws XMPPErrorException
  267.      * @throws NoResponseException
  268.      * @throws NotConnectedException
  269.      * @throws InterruptedException
  270.      */
  271.     public void deleteNode(String nodeId) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  272.     {
  273.         sendPubsubPacket(Type.set, new NodeExtension(PubSubElementType.DELETE, nodeId), PubSubElementType.DELETE.getNamespace());
  274.         nodeMap.remove(nodeId);
  275.     }
  276.    
  277.     /**
  278.      * Returns the default settings for Node configuration.
  279.      *
  280.      * @return configuration form containing the default settings.
  281.      * @throws XMPPErrorException
  282.      * @throws NoResponseException
  283.      * @throws NotConnectedException
  284.      * @throws InterruptedException
  285.      */
  286.     public ConfigureForm getDefaultConfiguration() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  287.     {
  288.         // Errors will cause exceptions in getReply, so it only returns
  289.         // on success.
  290.         PubSub reply = sendPubsubPacket(Type.get, new NodeExtension(PubSubElementType.DEFAULT), PubSubElementType.DEFAULT.getNamespace());
  291.         return NodeUtils.getFormFromPacket(reply, PubSubElementType.DEFAULT);
  292.     }
  293.    
  294.     /**
  295.      * Gets the supported features of the servers pubsub implementation
  296.      * as a standard {@link DiscoverInfo} instance.
  297.      *
  298.      * @return The supported features
  299.      * @throws XMPPErrorException
  300.      * @throws NoResponseException
  301.      * @throws NotConnectedException
  302.      * @throws InterruptedException
  303.      */
  304.     public DiscoverInfo getSupportedFeatures() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  305.     {
  306.         ServiceDiscoveryManager mgr = ServiceDiscoveryManager.getInstanceFor(con);
  307.         return mgr.discoverInfo(to);
  308.     }

  309.     private PubSub sendPubsubPacket(Type type, ExtensionElement ext, PubSubNamespace ns)
  310.                     throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  311.         return sendPubsubPacket(con, to, type, Collections.singletonList(ext), ns);
  312.     }

  313.     static PubSub sendPubsubPacket(XMPPConnection con, Jid to, Type type, List<ExtensionElement> extList, PubSubNamespace ns) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  314.     {
  315.         PubSub pubSub = new PubSub(to, type, ns);
  316.         for (ExtensionElement pe : extList) {
  317.             pubSub.addExtension(pe);
  318.         }
  319.         return sendPubsubPacket(con ,pubSub);
  320.     }

  321.     static PubSub sendPubsubPacket(XMPPConnection con, PubSub packet) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  322.     {
  323.         IQ resultIQ = con.createPacketCollectorAndSend(packet).nextResultOrThrow();
  324.         if (resultIQ instanceof EmptyResultIQ) {
  325.             return null;
  326.         }
  327.         return (PubSub) resultIQ;
  328.     }

  329. }