• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
Connection.addConnectionCreationListener(new ConnectionCreationListener() { public void connectionCreated(Connection connection) { new ServiceDiscoveryManager(connection); } })72         Connection.addConnectionCreationListener(new ConnectionCreationListener() {
73             public void connectionCreated(Connection connection) {
74                 new ServiceDiscoveryManager(connection);
75             }
76         });
identities.add(new Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, DEFAULT_IDENTITY_TYPE))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      */
ServiceDiscoveryManager(Connection connection)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      */
getInstanceFor(Connection connection)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      */
getIdentityName()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      */
setIdentityName(String name)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      */
getIdentityType()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      */
setIdentityType(String type)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      */
getIdentities()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      */
init()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      */
addDiscoverInfoTo(DiscoverInfo response)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      */
getNodeInformationProvider(String node)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      */
setNodeInformationProvider(String node, NodeInformationProvider listener)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      */
removeNodeInformationProvider(String node)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      */
getFeatures()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      */
getFeaturesList()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      */
addFeature(String feature)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      */
removeFeature(String feature)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      */
includesFeature(String feature)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      */
setExtendedInfo(DataForm info)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      */
getExtendedInfo()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      */
getExtendedInfoAsList()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      */
removeExtendedInfo()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      */
discoverInfo(String entityID)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      */
discoverInfo(String entityID, String node)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      */
discoverItems(String entityID)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      */
discoverItems(String entityID, String node)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@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      */
canPublishItems(String entityID)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@host.com).
629       *
630       * @param DiscoverInfo the discover info packet to check.
631       * @return true if the server supports publishing of items.
632       */
canPublishItems(DiscoverInfo info)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      */
publishItems(String entityID, DiscoverItems discoverItems)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      */
publishItems(String entityID, String node, DiscoverItems discoverItems)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      */
setEntityCapsManager(EntityCapsManager manager)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      */
renewEntityCapsVersion()704     private void renewEntityCapsVersion() {
705         if (capsManager != null && capsManager.entityCapsEnabled())
706             capsManager.updateLocalEntityCaps();
707     }
708 }
709