Home | History | Annotate | Download | only in smackx
      1 /**
      2  * $RCSfile$
      3  * $Revision$
      4  * $Date$
      5  *
      6  * Copyright 2003-2007 Jive Software.
      7  *
      8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
      9  * you may not use this file except in compliance with the License.
     10  * You may obtain a copy of the License at
     11  *
     12  *     http://www.apache.org/licenses/LICENSE-2.0
     13  *
     14  * Unless required by applicable law or agreed to in writing, software
     15  * distributed under the License is distributed on an "AS IS" BASIS,
     16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     17  * See the License for the specific language governing permissions and
     18  * limitations under the License.
     19  */
     20 
     21 package org.jivesoftware.smackx;
     22 
     23 import org.jivesoftware.smack.*;
     24 import org.jivesoftware.smack.filter.PacketFilter;
     25 import org.jivesoftware.smack.filter.PacketIDFilter;
     26 import org.jivesoftware.smack.filter.PacketTypeFilter;
     27 import org.jivesoftware.smack.packet.IQ;
     28 import org.jivesoftware.smack.packet.Packet;
     29 import org.jivesoftware.smack.packet.PacketExtension;
     30 import org.jivesoftware.smack.packet.XMPPError;
     31 import org.jivesoftware.smackx.entitycaps.EntityCapsManager;
     32 import org.jivesoftware.smackx.packet.DiscoverInfo;
     33 import org.jivesoftware.smackx.packet.DiscoverInfo.Identity;
     34 import org.jivesoftware.smackx.packet.DiscoverItems;
     35 import org.jivesoftware.smackx.packet.DataForm;
     36 
     37 import java.util.*;
     38 import java.util.concurrent.ConcurrentHashMap;
     39 
     40 /**
     41  * Manages discovery of services in XMPP entities. This class provides:
     42  * <ol>
     43  * <li>A registry of supported features in this XMPP entity.
     44  * <li>Automatic response when this XMPP entity is queried for information.
     45  * <li>Ability to discover items and information of remote XMPP entities.
     46  * <li>Ability to publish publicly available items.
     47  * </ol>
     48  *
     49  * @author Gaston Dombiak
     50  */
     51 public class ServiceDiscoveryManager {
     52 
     53     private static final String DEFAULT_IDENTITY_NAME = "Smack";
     54     private static final String DEFAULT_IDENTITY_CATEGORY = "client";
     55     private static final String DEFAULT_IDENTITY_TYPE = "pc";
     56 
     57     private static List<DiscoverInfo.Identity> identities = new LinkedList<DiscoverInfo.Identity>();
     58 
     59     private EntityCapsManager capsManager;
     60 
     61     private static Map<Connection, ServiceDiscoveryManager> instances =
     62             new ConcurrentHashMap<Connection, ServiceDiscoveryManager>();
     63 
     64     private Connection connection;
     65     private final Set<String> features = new HashSet<String>();
     66     private DataForm extendedInfo = null;
     67     private Map<String, NodeInformationProvider> nodeInformationProviders =
     68             new ConcurrentHashMap<String, NodeInformationProvider>();
     69 
     70     // Create a new ServiceDiscoveryManager on every established connection
     71     static {
     72         Connection.addConnectionCreationListener(new ConnectionCreationListener() {
     73             public void connectionCreated(Connection connection) {
     74                 new ServiceDiscoveryManager(connection);
     75             }
     76         });
     77         identities.add(new Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, DEFAULT_IDENTITY_TYPE));
     78     }
     79 
     80     /**
     81      * Creates a new ServiceDiscoveryManager for a given Connection. This means that the
     82      * service manager will respond to any service discovery request that the connection may
     83      * receive.
     84      *
     85      * @param connection the connection to which a ServiceDiscoveryManager is going to be created.
     86      */
     87     public ServiceDiscoveryManager(Connection connection) {
     88         this.connection = connection;
     89 
     90         init();
     91     }
     92 
     93     /**
     94      * Returns the ServiceDiscoveryManager instance associated with a given Connection.
     95      *
     96      * @param connection the connection used to look for the proper ServiceDiscoveryManager.
     97      * @return the ServiceDiscoveryManager associated with a given Connection.
     98      */
     99     public static ServiceDiscoveryManager getInstanceFor(Connection connection) {
    100         return instances.get(connection);
    101     }
    102 
    103     /**
    104      * Returns the name of the client that will be returned when asked for the client identity
    105      * in a disco request. The name could be any value you need to identity this client.
    106      *
    107      * @return the name of the client that will be returned when asked for the client identity
    108      *          in a disco request.
    109      */
    110     public static String getIdentityName() {
    111         DiscoverInfo.Identity identity = identities.get(0);
    112         if (identity != null) {
    113             return identity.getName();
    114         } else {
    115             return null;
    116         }
    117     }
    118 
    119     /**
    120      * Sets the name of the client that will be returned when asked for the client identity
    121      * in a disco request. The name could be any value you need to identity this client.
    122      *
    123      * @param name the name of the client that will be returned when asked for the client identity
    124      *          in a disco request.
    125      */
    126     public static void setIdentityName(String name) {
    127         DiscoverInfo.Identity identity = identities.remove(0);
    128         identity = new DiscoverInfo.Identity(DEFAULT_IDENTITY_CATEGORY, name, DEFAULT_IDENTITY_TYPE);
    129         identities.add(identity);
    130     }
    131 
    132     /**
    133      * Returns the type of client that will be returned when asked for the client identity in a
    134      * disco request. The valid types are defined by the category client. Follow this link to learn
    135      * the possible types: <a href="http://xmpp.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
    136      *
    137      * @return the type of client that will be returned when asked for the client identity in a
    138      *          disco request.
    139      */
    140     public static String getIdentityType() {
    141         DiscoverInfo.Identity identity = identities.get(0);
    142         if (identity != null) {
    143             return identity.getType();
    144         } else {
    145             return null;
    146         }
    147     }
    148 
    149     /**
    150      * Sets the type of client that will be returned when asked for the client identity in a
    151      * disco request. The valid types are defined by the category client. Follow this link to learn
    152      * the possible types: <a href="http://xmpp.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
    153      *
    154      * @param type the type of client that will be returned when asked for the client identity in a
    155      *          disco request.
    156      */
    157     public static void setIdentityType(String type) {
    158         DiscoverInfo.Identity identity = identities.get(0);
    159         if (identity != null) {
    160             identity.setType(type);
    161         } else {
    162             identity = new DiscoverInfo.Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, type);
    163             identities.add(identity);
    164         }
    165     }
    166 
    167     /**
    168      * Returns all identities of this client as unmodifiable Collection
    169      *
    170      * @return
    171      */
    172     public static List<DiscoverInfo.Identity> getIdentities() {
    173         return Collections.unmodifiableList(identities);
    174     }
    175 
    176     /**
    177      * Initializes the packet listeners of the connection that will answer to any
    178      * service discovery request.
    179      */
    180     private void init() {
    181         // Register the new instance and associate it with the connection
    182         instances.put(connection, this);
    183 
    184         addFeature(DiscoverInfo.NAMESPACE);
    185         addFeature(DiscoverItems.NAMESPACE);
    186 
    187         // Add a listener to the connection that removes the registered instance when
    188         // the connection is closed
    189         connection.addConnectionListener(new ConnectionListener() {
    190             public void connectionClosed() {
    191                 // Unregister this instance since the connection has been closed
    192                 instances.remove(connection);
    193             }
    194 
    195             public void connectionClosedOnError(Exception e) {
    196                 // ignore
    197             }
    198 
    199             public void reconnectionFailed(Exception e) {
    200                 // ignore
    201             }
    202 
    203             public void reconnectingIn(int seconds) {
    204                 // ignore
    205             }
    206 
    207             public void reconnectionSuccessful() {
    208                 // ignore
    209             }
    210         });
    211 
    212         // Listen for disco#items requests and answer with an empty result
    213         PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class);
    214         PacketListener packetListener = new PacketListener() {
    215             public void processPacket(Packet packet) {
    216                 DiscoverItems discoverItems = (DiscoverItems) packet;
    217                 // Send back the items defined in the client if the request is of type GET
    218                 if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) {
    219                     DiscoverItems response = new DiscoverItems();
    220                     response.setType(IQ.Type.RESULT);
    221                     response.setTo(discoverItems.getFrom());
    222                     response.setPacketID(discoverItems.getPacketID());
    223                     response.setNode(discoverItems.getNode());
    224 
    225                     // Add the defined items related to the requested node. Look for
    226                     // the NodeInformationProvider associated with the requested node.
    227                     NodeInformationProvider nodeInformationProvider =
    228                             getNodeInformationProvider(discoverItems.getNode());
    229                     if (nodeInformationProvider != null) {
    230                         // Specified node was found, add node items
    231                         response.addItems(nodeInformationProvider.getNodeItems());
    232                         // Add packet extensions
    233                         response.addExtensions(nodeInformationProvider.getNodePacketExtensions());
    234                     } else if(discoverItems.getNode() != null) {
    235                         // Return <item-not-found/> error since client doesn't contain
    236                         // the specified node
    237                         response.setType(IQ.Type.ERROR);
    238                         response.setError(new XMPPError(XMPPError.Condition.item_not_found));
    239                     }
    240                     connection.sendPacket(response);
    241                 }
    242             }
    243         };
    244         connection.addPacketListener(packetListener, packetFilter);
    245 
    246         // Listen for disco#info requests and answer the client's supported features
    247         // To add a new feature as supported use the #addFeature message
    248         packetFilter = new PacketTypeFilter(DiscoverInfo.class);
    249         packetListener = new PacketListener() {
    250             public void processPacket(Packet packet) {
    251                 DiscoverInfo discoverInfo = (DiscoverInfo) packet;
    252                 // Answer the client's supported features if the request is of the GET type
    253                 if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) {
    254                     DiscoverInfo response = new DiscoverInfo();
    255                     response.setType(IQ.Type.RESULT);
    256                     response.setTo(discoverInfo.getFrom());
    257                     response.setPacketID(discoverInfo.getPacketID());
    258                     response.setNode(discoverInfo.getNode());
    259                     // Add the client's identity and features only if "node" is null
    260                     // and if the request was not send to a node. If Entity Caps are
    261                     // enabled the client's identity and features are may also added
    262                     // if the right node is chosen
    263                     if (discoverInfo.getNode() == null) {
    264                         addDiscoverInfoTo(response);
    265                     }
    266                     else {
    267                         // Disco#info was sent to a node. Check if we have information of the
    268                         // specified node
    269                         NodeInformationProvider nodeInformationProvider =
    270                                 getNodeInformationProvider(discoverInfo.getNode());
    271                         if (nodeInformationProvider != null) {
    272                             // Node was found. Add node features
    273                             response.addFeatures(nodeInformationProvider.getNodeFeatures());
    274                             // Add node identities
    275                             response.addIdentities(nodeInformationProvider.getNodeIdentities());
    276                             // Add packet extensions
    277                             response.addExtensions(nodeInformationProvider.getNodePacketExtensions());
    278                         }
    279                         else {
    280                             // Return <item-not-found/> error since specified node was not found
    281                             response.setType(IQ.Type.ERROR);
    282                             response.setError(new XMPPError(XMPPError.Condition.item_not_found));
    283                         }
    284                     }
    285                     connection.sendPacket(response);
    286                 }
    287             }
    288         };
    289         connection.addPacketListener(packetListener, packetFilter);
    290     }
    291 
    292     /**
    293      * Add discover info response data.
    294      *
    295      * @see <a href="http://xmpp.org/extensions/xep-0030.html#info-basic">XEP-30 Basic Protocol; Example 2</a>
    296      *
    297      * @param response the discover info response packet
    298      */
    299     public void addDiscoverInfoTo(DiscoverInfo response) {
    300         // First add the identities of the connection
    301         response.addIdentities(identities);
    302 
    303         // Add the registered features to the response
    304         synchronized (features) {
    305             for (Iterator<String> it = getFeatures(); it.hasNext();) {
    306                 response.addFeature(it.next());
    307             }
    308             response.addExtension(extendedInfo);
    309         }
    310     }
    311 
    312     /**
    313      * Returns the NodeInformationProvider responsible for providing information
    314      * (ie items) related to a given node or <tt>null</null> if none.<p>
    315      *
    316      * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
    317      * NodeInformationProvider will provide information about the rooms where the user has joined.
    318      *
    319      * @param node the node that contains items associated with an entity not addressable as a JID.
    320      * @return the NodeInformationProvider responsible for providing information related
    321      * to a given node.
    322      */
    323     private NodeInformationProvider getNodeInformationProvider(String node) {
    324         if (node == null) {
    325             return null;
    326         }
    327         return nodeInformationProviders.get(node);
    328     }
    329 
    330     /**
    331      * Sets the NodeInformationProvider responsible for providing information
    332      * (ie items) related to a given node. Every time this client receives a disco request
    333      * regarding the items of a given node, the provider associated to that node will be the
    334      * responsible for providing the requested information.<p>
    335      *
    336      * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
    337      * NodeInformationProvider will provide information about the rooms where the user has joined.
    338      *
    339      * @param node the node whose items will be provided by the NodeInformationProvider.
    340      * @param listener the NodeInformationProvider responsible for providing items related
    341      *      to the node.
    342      */
    343     public void setNodeInformationProvider(String node, NodeInformationProvider listener) {
    344         nodeInformationProviders.put(node, listener);
    345     }
    346 
    347     /**
    348      * Removes the NodeInformationProvider responsible for providing information
    349      * (ie items) related to a given node. This means that no more information will be
    350      * available for the specified node.
    351      *
    352      * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
    353      * NodeInformationProvider will provide information about the rooms where the user has joined.
    354      *
    355      * @param node the node to remove the associated NodeInformationProvider.
    356      */
    357     public void removeNodeInformationProvider(String node) {
    358         nodeInformationProviders.remove(node);
    359     }
    360 
    361     /**
    362      * Returns the supported features by this XMPP entity.
    363      *
    364      * @return an Iterator on the supported features by this XMPP entity.
    365      */
    366     public Iterator<String> getFeatures() {
    367         synchronized (features) {
    368             return Collections.unmodifiableList(new ArrayList<String>(features)).iterator();
    369         }
    370     }
    371 
    372     /**
    373      * Returns the supported features by this XMPP entity.
    374      *
    375      * @return a copy of the List on the supported features by this XMPP entity.
    376      */
    377     public List<String> getFeaturesList() {
    378         synchronized (features) {
    379             return new LinkedList<String>(features);
    380         }
    381     }
    382 
    383     /**
    384      * Registers that a new feature is supported by this XMPP entity. When this client is
    385      * queried for its information the registered features will be answered.<p>
    386      *
    387      * Since no packet is actually sent to the server it is safe to perform this operation
    388      * before logging to the server. In fact, you may want to configure the supported features
    389      * before logging to the server so that the information is already available if it is required
    390      * upon login.
    391      *
    392      * @param feature the feature to register as supported.
    393      */
    394     public void addFeature(String feature) {
    395         synchronized (features) {
    396             features.add(feature);
    397             renewEntityCapsVersion();
    398         }
    399     }
    400 
    401     /**
    402      * Removes the specified feature from the supported features by this XMPP entity.<p>
    403      *
    404      * Since no packet is actually sent to the server it is safe to perform this operation
    405      * before logging to the server.
    406      *
    407      * @param feature the feature to remove from the supported features.
    408      */
    409     public void removeFeature(String feature) {
    410         synchronized (features) {
    411             features.remove(feature);
    412             renewEntityCapsVersion();
    413         }
    414     }
    415 
    416     /**
    417      * Returns true if the specified feature is registered in the ServiceDiscoveryManager.
    418      *
    419      * @param feature the feature to look for.
    420      * @return a boolean indicating if the specified featured is registered or not.
    421      */
    422     public boolean includesFeature(String feature) {
    423         synchronized (features) {
    424             return features.contains(feature);
    425         }
    426     }
    427 
    428     /**
    429      * Registers extended discovery information of this XMPP entity. When this
    430      * client is queried for its information this data form will be returned as
    431      * specified by XEP-0128.
    432      * <p>
    433      *
    434      * Since no packet is actually sent to the server it is safe to perform this
    435      * operation before logging to the server. In fact, you may want to
    436      * configure the extended info before logging to the server so that the
    437      * information is already available if it is required upon login.
    438      *
    439      * @param info
    440      *            the data form that contains the extend service discovery
    441      *            information.
    442      */
    443     public void setExtendedInfo(DataForm info) {
    444       extendedInfo = info;
    445       renewEntityCapsVersion();
    446     }
    447 
    448     /**
    449      * Returns the data form that is set as extended information for this Service Discovery instance (XEP-0128)
    450      *
    451      * @see <a href="http://xmpp.org/extensions/xep-0128.html">XEP-128: Service Discovery Extensions</a>
    452      * @return
    453      */
    454     public DataForm getExtendedInfo() {
    455         return extendedInfo;
    456     }
    457 
    458     /**
    459      * Returns the data form as List of PacketExtensions, or null if no data form is set.
    460      * This representation is needed by some classes (e.g. EntityCapsManager, NodeInformationProvider)
    461      *
    462      * @return
    463      */
    464     public List<PacketExtension> getExtendedInfoAsList() {
    465         List<PacketExtension> res = null;
    466         if (extendedInfo != null) {
    467             res = new ArrayList<PacketExtension>(1);
    468             res.add(extendedInfo);
    469         }
    470         return res;
    471     }
    472 
    473     /**
    474      * Removes the data form containing extended service discovery information
    475      * from the information returned by this XMPP entity.<p>
    476      *
    477      * Since no packet is actually sent to the server it is safe to perform this
    478      * operation before logging to the server.
    479      */
    480     public void removeExtendedInfo() {
    481        extendedInfo = null;
    482        renewEntityCapsVersion();
    483     }
    484 
    485     /**
    486      * Returns the discovered information of a given XMPP entity addressed by its JID.
    487      * Use null as entityID to query the server
    488      *
    489      * @param entityID the address of the XMPP entity or null.
    490      * @return the discovered information.
    491      * @throws XMPPException if the operation failed for some reason.
    492      */
    493     public DiscoverInfo discoverInfo(String entityID) throws XMPPException {
    494         if (entityID == null)
    495             return discoverInfo(null, null);
    496 
    497         // Check if the have it cached in the Entity Capabilities Manager
    498         DiscoverInfo info = EntityCapsManager.getDiscoverInfoByUser(entityID);
    499 
    500         if (info != null) {
    501             // We were able to retrieve the information from Entity Caps and
    502             // avoided a disco request, hurray!
    503             return info;
    504         }
    505 
    506         // Try to get the newest node#version if it's known, otherwise null is
    507         // returned
    508         EntityCapsManager.NodeVerHash nvh = EntityCapsManager.getNodeVerHashByJid(entityID);
    509 
    510         // Discover by requesting the information from the remote entity
    511         // Note that wee need to use NodeVer as argument for Node if it exists
    512         info = discoverInfo(entityID, nvh != null ? nvh.getNodeVer() : null);
    513 
    514         // If the node version is known, store the new entry.
    515         if (nvh != null) {
    516             if (EntityCapsManager.verifyDiscoverInfoVersion(nvh.getVer(), nvh.getHash(), info))
    517                 EntityCapsManager.addDiscoverInfoByNode(nvh.getNodeVer(), info);
    518         }
    519 
    520         return info;
    521     }
    522 
    523     /**
    524      * Returns the discovered information of a given XMPP entity addressed by its JID and
    525      * note attribute. Use this message only when trying to query information which is not
    526      * directly addressable.
    527      *
    528      * @see <a href="http://xmpp.org/extensions/xep-0030.html#info-basic">XEP-30 Basic Protocol</a>
    529      * @see <a href="http://xmpp.org/extensions/xep-0030.html#info-nodes">XEP-30 Info Nodes</a>
    530      *
    531      * @param entityID the address of the XMPP entity.
    532      * @param node the optional attribute that supplements the 'jid' attribute.
    533      * @return the discovered information.
    534      * @throws XMPPException if the operation failed for some reason.
    535      */
    536     public DiscoverInfo discoverInfo(String entityID, String node) throws XMPPException {
    537         // Discover the entity's info
    538         DiscoverInfo disco = new DiscoverInfo();
    539         disco.setType(IQ.Type.GET);
    540         disco.setTo(entityID);
    541         disco.setNode(node);
    542 
    543         // Create a packet collector to listen for a response.
    544         PacketCollector collector =
    545             connection.createPacketCollector(new PacketIDFilter(disco.getPacketID()));
    546 
    547         connection.sendPacket(disco);
    548 
    549         // Wait up to 5 seconds for a result.
    550         IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
    551         // Stop queuing results
    552         collector.cancel();
    553         if (result == null) {
    554             throw new XMPPException("No response from the server.");
    555         }
    556         if (result.getType() == IQ.Type.ERROR) {
    557             throw new XMPPException(result.getError());
    558         }
    559         return (DiscoverInfo) result;
    560     }
    561 
    562     /**
    563      * Returns the discovered items of a given XMPP entity addressed by its JID.
    564      *
    565      * @param entityID the address of the XMPP entity.
    566      * @return the discovered information.
    567      * @throws XMPPException if the operation failed for some reason.
    568      */
    569     public DiscoverItems discoverItems(String entityID) throws XMPPException {
    570         return discoverItems(entityID, null);
    571     }
    572 
    573     /**
    574      * Returns the discovered items of a given XMPP entity addressed by its JID and
    575      * note attribute. Use this message only when trying to query information which is not
    576      * directly addressable.
    577      *
    578      * @param entityID the address of the XMPP entity.
    579      * @param node the optional attribute that supplements the 'jid' attribute.
    580      * @return the discovered items.
    581      * @throws XMPPException if the operation failed for some reason.
    582      */
    583     public DiscoverItems discoverItems(String entityID, String node) throws XMPPException {
    584         // Discover the entity's items
    585         DiscoverItems disco = new DiscoverItems();
    586         disco.setType(IQ.Type.GET);
    587         disco.setTo(entityID);
    588         disco.setNode(node);
    589 
    590         // Create a packet collector to listen for a response.
    591         PacketCollector collector =
    592             connection.createPacketCollector(new PacketIDFilter(disco.getPacketID()));
    593 
    594         connection.sendPacket(disco);
    595 
    596         // Wait up to 5 seconds for a result.
    597         IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
    598         // Stop queuing results
    599         collector.cancel();
    600         if (result == null) {
    601             throw new XMPPException("No response from the server.");
    602         }
    603         if (result.getType() == IQ.Type.ERROR) {
    604             throw new XMPPException(result.getError());
    605         }
    606         return (DiscoverItems) result;
    607     }
    608 
    609     /**
    610      * Returns true if the server supports publishing of items. A client may wish to publish items
    611      * to the server so that the server can provide items associated to the client. These items will
    612      * be returned by the server whenever the server receives a disco request targeted to the bare
    613      * address of the client (i.e. user (at) host.com).
    614      *
    615      * @param entityID the address of the XMPP entity.
    616      * @return true if the server supports publishing of items.
    617      * @throws XMPPException if the operation failed for some reason.
    618      */
    619     public boolean canPublishItems(String entityID) throws XMPPException {
    620         DiscoverInfo info = discoverInfo(entityID);
    621         return canPublishItems(info);
    622      }
    623 
    624      /**
    625       * Returns true if the server supports publishing of items. A client may wish to publish items
    626       * to the server so that the server can provide items associated to the client. These items will
    627       * be returned by the server whenever the server receives a disco request targeted to the bare
    628       * address of the client (i.e. user (at) host.com).
    629       *
    630       * @param DiscoverInfo the discover info packet to check.
    631       * @return true if the server supports publishing of items.
    632       */
    633      public static boolean canPublishItems(DiscoverInfo info) {
    634          return info.containsFeature("http://jabber.org/protocol/disco#publish");
    635      }
    636 
    637     /**
    638      * Publishes new items to a parent entity. The item elements to publish MUST have at least
    639      * a 'jid' attribute specifying the Entity ID of the item, and an action attribute which
    640      * specifies the action being taken for that item. Possible action values are: "update" and
    641      * "remove".
    642      *
    643      * @param entityID the address of the XMPP entity.
    644      * @param discoverItems the DiscoveryItems to publish.
    645      * @throws XMPPException if the operation failed for some reason.
    646      */
    647     public void publishItems(String entityID, DiscoverItems discoverItems)
    648             throws XMPPException {
    649         publishItems(entityID, null, discoverItems);
    650     }
    651 
    652     /**
    653      * Publishes new items to a parent entity and node. The item elements to publish MUST have at
    654      * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which
    655      * specifies the action being taken for that item. Possible action values are: "update" and
    656      * "remove".
    657      *
    658      * @param entityID the address of the XMPP entity.
    659      * @param node the attribute that supplements the 'jid' attribute.
    660      * @param discoverItems the DiscoveryItems to publish.
    661      * @throws XMPPException if the operation failed for some reason.
    662      */
    663     public void publishItems(String entityID, String node, DiscoverItems discoverItems)
    664             throws XMPPException {
    665         discoverItems.setType(IQ.Type.SET);
    666         discoverItems.setTo(entityID);
    667         discoverItems.setNode(node);
    668 
    669         // Create a packet collector to listen for a response.
    670         PacketCollector collector =
    671             connection.createPacketCollector(new PacketIDFilter(discoverItems.getPacketID()));
    672 
    673         connection.sendPacket(discoverItems);
    674 
    675         // Wait up to 5 seconds for a result.
    676         IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout());
    677         // Stop queuing results
    678         collector.cancel();
    679         if (result == null) {
    680             throw new XMPPException("No response from the server.");
    681         }
    682         if (result.getType() == IQ.Type.ERROR) {
    683             throw new XMPPException(result.getError());
    684         }
    685     }
    686 
    687     /**
    688      * Entity Capabilities
    689      */
    690 
    691     /**
    692      * Loads the ServiceDiscoveryManager with an EntityCapsManger
    693      * that speeds up certain lookups
    694      * @param manager
    695      */
    696     public void setEntityCapsManager(EntityCapsManager manager) {
    697         capsManager = manager;
    698     }
    699 
    700     /**
    701      * Updates the Entity Capabilities Verification String
    702      * if EntityCaps is enabled
    703      */
    704     private void renewEntityCapsVersion() {
    705         if (capsManager != null && capsManager.entityCapsEnabled())
    706             capsManager.updateLocalEntityCaps();
    707     }
    708 }
    709