001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.disco; 018 019import org.jivesoftware.smack.SmackException.NoResponseException; 020import org.jivesoftware.smack.SmackException.NotConnectedException; 021import org.jivesoftware.smack.XMPPConnection; 022import org.jivesoftware.smack.ConnectionCreationListener; 023import org.jivesoftware.smack.Manager; 024import org.jivesoftware.smack.PacketListener; 025import org.jivesoftware.smack.XMPPException.XMPPErrorException; 026import org.jivesoftware.smack.filter.PacketFilter; 027import org.jivesoftware.smack.filter.PacketTypeFilter; 028import org.jivesoftware.smack.packet.IQ; 029import org.jivesoftware.smack.packet.Packet; 030import org.jivesoftware.smack.packet.PacketExtension; 031import org.jivesoftware.smack.packet.XMPPError; 032import org.jivesoftware.smackx.caps.EntityCapsManager; 033import org.jivesoftware.smackx.disco.packet.DiscoverInfo; 034import org.jivesoftware.smackx.disco.packet.DiscoverItems; 035import org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity; 036import org.jivesoftware.smackx.xdata.packet.DataForm; 037 038import java.util.ArrayList; 039import java.util.Collections; 040import java.util.HashSet; 041import java.util.LinkedList; 042import java.util.List; 043import java.util.Map; 044import java.util.Set; 045import java.util.WeakHashMap; 046import java.util.concurrent.ConcurrentHashMap; 047 048/** 049 * Manages discovery of services in XMPP entities. This class provides: 050 * <ol> 051 * <li>A registry of supported features in this XMPP entity. 052 * <li>Automatic response when this XMPP entity is queried for information. 053 * <li>Ability to discover items and information of remote XMPP entities. 054 * <li>Ability to publish publicly available items. 055 * </ol> 056 * 057 * @author Gaston Dombiak 058 */ 059public class ServiceDiscoveryManager extends Manager { 060 061 private static final String DEFAULT_IDENTITY_NAME = "Smack"; 062 private static final String DEFAULT_IDENTITY_CATEGORY = "client"; 063 private static final String DEFAULT_IDENTITY_TYPE = "pc"; 064 private static DiscoverInfo.Identity defaultIdentity = new Identity(DEFAULT_IDENTITY_CATEGORY, 065 DEFAULT_IDENTITY_NAME, DEFAULT_IDENTITY_TYPE); 066 067 private Set<DiscoverInfo.Identity> identities = new HashSet<DiscoverInfo.Identity>(); 068 private DiscoverInfo.Identity identity = defaultIdentity; 069 070 private EntityCapsManager capsManager; 071 072 private static Map<XMPPConnection, ServiceDiscoveryManager> instances = 073 Collections.synchronizedMap(new WeakHashMap<XMPPConnection, ServiceDiscoveryManager>()); 074 075 private final Set<String> features = new HashSet<String>(); 076 private DataForm extendedInfo = null; 077 private Map<String, NodeInformationProvider> nodeInformationProviders = 078 new ConcurrentHashMap<String, NodeInformationProvider>(); 079 080 // Create a new ServiceDiscoveryManager on every established connection 081 static { 082 XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() { 083 public void connectionCreated(XMPPConnection connection) { 084 getInstanceFor(connection); 085 } 086 }); 087 } 088 089 /** 090 * Set the default identity all new connections will have. If unchanged the default identity is an 091 * identity where category is set to 'client', type is set to 'pc' and name is set to 'Smack'. 092 * 093 * @param identity 094 */ 095 public static void setDefaultIdentity(DiscoverInfo.Identity identity) { 096 defaultIdentity = identity; 097 } 098 099 /** 100 * Creates a new ServiceDiscoveryManager for a given XMPPConnection. This means that the 101 * service manager will respond to any service discovery request that the connection may 102 * receive. 103 * 104 * @param connection the connection to which a ServiceDiscoveryManager is going to be created. 105 */ 106 private ServiceDiscoveryManager(XMPPConnection connection) { 107 super(connection); 108 // Register the new instance and associate it with the connection 109 instances.put(connection, this); 110 111 addFeature(DiscoverInfo.NAMESPACE); 112 addFeature(DiscoverItems.NAMESPACE); 113 114 // Listen for disco#items requests and answer with an empty result 115 PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class); 116 PacketListener packetListener = new PacketListener() { 117 public void processPacket(Packet packet) throws NotConnectedException { 118 XMPPConnection connection = connection(); 119 if (connection == null) return; 120 DiscoverItems discoverItems = (DiscoverItems) packet; 121 // Send back the items defined in the client if the request is of type GET 122 if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) { 123 DiscoverItems response = new DiscoverItems(); 124 response.setType(IQ.Type.RESULT); 125 response.setTo(discoverItems.getFrom()); 126 response.setPacketID(discoverItems.getPacketID()); 127 response.setNode(discoverItems.getNode()); 128 129 // Add the defined items related to the requested node. Look for 130 // the NodeInformationProvider associated with the requested node. 131 NodeInformationProvider nodeInformationProvider = 132 getNodeInformationProvider(discoverItems.getNode()); 133 if (nodeInformationProvider != null) { 134 // Specified node was found, add node items 135 response.addItems(nodeInformationProvider.getNodeItems()); 136 // Add packet extensions 137 response.addExtensions(nodeInformationProvider.getNodePacketExtensions()); 138 } else if(discoverItems.getNode() != null) { 139 // Return <item-not-found/> error since client doesn't contain 140 // the specified node 141 response.setType(IQ.Type.ERROR); 142 response.setError(new XMPPError(XMPPError.Condition.item_not_found)); 143 } 144 connection.sendPacket(response); 145 } 146 } 147 }; 148 connection.addPacketListener(packetListener, packetFilter); 149 150 // Listen for disco#info requests and answer the client's supported features 151 // To add a new feature as supported use the #addFeature message 152 packetFilter = new PacketTypeFilter(DiscoverInfo.class); 153 packetListener = new PacketListener() { 154 public void processPacket(Packet packet) throws NotConnectedException { 155 XMPPConnection connection = connection(); 156 if (connection == null) return; 157 DiscoverInfo discoverInfo = (DiscoverInfo) packet; 158 // Answer the client's supported features if the request is of the GET type 159 if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) { 160 DiscoverInfo response = new DiscoverInfo(); 161 response.setType(IQ.Type.RESULT); 162 response.setTo(discoverInfo.getFrom()); 163 response.setPacketID(discoverInfo.getPacketID()); 164 response.setNode(discoverInfo.getNode()); 165 // Add the client's identity and features only if "node" is null 166 // and if the request was not send to a node. If Entity Caps are 167 // enabled the client's identity and features are may also added 168 // if the right node is chosen 169 if (discoverInfo.getNode() == null) { 170 addDiscoverInfoTo(response); 171 } 172 else { 173 // Disco#info was sent to a node. Check if we have information of the 174 // specified node 175 NodeInformationProvider nodeInformationProvider = 176 getNodeInformationProvider(discoverInfo.getNode()); 177 if (nodeInformationProvider != null) { 178 // Node was found. Add node features 179 response.addFeatures(nodeInformationProvider.getNodeFeatures()); 180 // Add node identities 181 response.addIdentities(nodeInformationProvider.getNodeIdentities()); 182 // Add packet extensions 183 response.addExtensions(nodeInformationProvider.getNodePacketExtensions()); 184 } 185 else { 186 // Return <item-not-found/> error since specified node was not found 187 response.setType(IQ.Type.ERROR); 188 response.setError(new XMPPError(XMPPError.Condition.item_not_found)); 189 } 190 } 191 connection.sendPacket(response); 192 } 193 } 194 }; 195 connection.addPacketListener(packetListener, packetFilter); 196 } 197 198 /** 199 * Returns the name of the client that will be returned when asked for the client identity 200 * in a disco request. The name could be any value you need to identity this client. 201 * 202 * @return the name of the client that will be returned when asked for the client identity 203 * in a disco request. 204 */ 205 public String getIdentityName() { 206 return identity.getName(); 207 } 208 209 /** 210 * Sets the name of the client that will be returned when asked for the client identity 211 * in a disco request. The name could be any value you need to identity this client. 212 * 213 * @param name the name of the client that will be returned when asked for the client identity 214 * in a disco request. 215 */ 216 public void setIdentityName(String name) { 217 identity.setName(name); 218 renewEntityCapsVersion(); 219 } 220 221 /** 222 * Sets the default identity the client will report. 223 * 224 * @param identity 225 */ 226 public void setIdentity(Identity identity) { 227 if (identity == null) throw new IllegalArgumentException("Identity can not be null"); 228 this.identity = identity; 229 renewEntityCapsVersion(); 230 } 231 232 /** 233 * Return the default identity of the client. 234 * 235 * @return the default identity. 236 */ 237 public Identity getIdentity() { 238 return identity; 239 } 240 241 /** 242 * Returns the type of client that will be returned when asked for the client identity in a 243 * disco request. The valid types are defined by the category client. Follow this link to learn 244 * the possible types: <a href="http://xmpp.org/registrar/disco-categories.html#client">Jabber::Registrar</a>. 245 * 246 * @return the type of client that will be returned when asked for the client identity in a 247 * disco request. 248 */ 249 public String getIdentityType() { 250 return identity.getType(); 251 } 252 253 /** 254 * Add an further identity to the client. 255 * 256 * @param identity 257 */ 258 public void addIdentity(DiscoverInfo.Identity identity) { 259 identities.add(identity); 260 renewEntityCapsVersion(); 261 } 262 263 /** 264 * Remove an identity from the client. Note that the client needs at least one identity, the default identity, which 265 * can not be removed. 266 * 267 * @param identity 268 * @return true, if successful. Otherwise the default identity was given. 269 */ 270 public boolean removeIdentity(DiscoverInfo.Identity identity) { 271 if (identity.equals(this.identity)) return false; 272 identities.remove(identity); 273 renewEntityCapsVersion(); 274 return true; 275 } 276 277 /** 278 * Returns all identities of this client as unmodifiable Collection 279 * 280 * @return all identies as set 281 */ 282 public Set<DiscoverInfo.Identity> getIdentities() { 283 Set<Identity> res = new HashSet<Identity>(identities); 284 // Add the default identity that must exist 285 res.add(defaultIdentity); 286 return Collections.unmodifiableSet(res); 287 } 288 289 /** 290 * Returns the ServiceDiscoveryManager instance associated with a given XMPPConnection. 291 * 292 * @param connection the connection used to look for the proper ServiceDiscoveryManager. 293 * @return the ServiceDiscoveryManager associated with a given XMPPConnection. 294 */ 295 public static synchronized ServiceDiscoveryManager getInstanceFor(XMPPConnection connection) { 296 ServiceDiscoveryManager sdm = instances.get(connection); 297 if (sdm == null) { 298 sdm = new ServiceDiscoveryManager(connection); 299 } 300 return sdm; 301 } 302 303 /** 304 * Add discover info response data. 305 * 306 * @see <a href="http://xmpp.org/extensions/xep-0030.html#info-basic">XEP-30 Basic Protocol; Example 2</a> 307 * 308 * @param response the discover info response packet 309 */ 310 public void addDiscoverInfoTo(DiscoverInfo response) { 311 // First add the identities of the connection 312 response.addIdentities(getIdentities()); 313 314 // Add the registered features to the response 315 synchronized (features) { 316 for (String feature : getFeatures()) { 317 response.addFeature(feature); 318 } 319 response.addExtension(extendedInfo); 320 } 321 } 322 323 /** 324 * Returns the NodeInformationProvider responsible for providing information 325 * (ie items) related to a given node or <tt>null</null> if none.<p> 326 * 327 * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the 328 * NodeInformationProvider will provide information about the rooms where the user has joined. 329 * 330 * @param node the node that contains items associated with an entity not addressable as a JID. 331 * @return the NodeInformationProvider responsible for providing information related 332 * to a given node. 333 */ 334 private NodeInformationProvider getNodeInformationProvider(String node) { 335 if (node == null) { 336 return null; 337 } 338 return nodeInformationProviders.get(node); 339 } 340 341 /** 342 * Sets the NodeInformationProvider responsible for providing information 343 * (ie items) related to a given node. Every time this client receives a disco request 344 * regarding the items of a given node, the provider associated to that node will be the 345 * responsible for providing the requested information.<p> 346 * 347 * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the 348 * NodeInformationProvider will provide information about the rooms where the user has joined. 349 * 350 * @param node the node whose items will be provided by the NodeInformationProvider. 351 * @param listener the NodeInformationProvider responsible for providing items related 352 * to the node. 353 */ 354 public void setNodeInformationProvider(String node, NodeInformationProvider listener) { 355 nodeInformationProviders.put(node, listener); 356 } 357 358 /** 359 * Removes the NodeInformationProvider responsible for providing information 360 * (ie items) related to a given node. This means that no more information will be 361 * available for the specified node. 362 * 363 * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the 364 * NodeInformationProvider will provide information about the rooms where the user has joined. 365 * 366 * @param node the node to remove the associated NodeInformationProvider. 367 */ 368 public void removeNodeInformationProvider(String node) { 369 nodeInformationProviders.remove(node); 370 } 371 372 /** 373 * Returns the supported features by this XMPP entity. 374 * 375 * @return a List of the supported features by this XMPP entity. 376 */ 377 public List<String> getFeatures() { 378 synchronized (features) { 379 return Collections.unmodifiableList(new ArrayList<String>(features)); 380 } 381 } 382 383 /** 384 * Returns the supported features by this XMPP entity. 385 * 386 * @return a copy of the List on the supported features by this XMPP entity. 387 */ 388 public List<String> getFeaturesList() { 389 synchronized (features) { 390 return new LinkedList<String>(features); 391 } 392 } 393 394 /** 395 * Registers that a new feature is supported by this XMPP entity. When this client is 396 * queried for its information the registered features will be answered.<p> 397 * 398 * Since no packet is actually sent to the server it is safe to perform this operation 399 * before logging to the server. In fact, you may want to configure the supported features 400 * before logging to the server so that the information is already available if it is required 401 * upon login. 402 * 403 * @param feature the feature to register as supported. 404 */ 405 public void addFeature(String feature) { 406 synchronized (features) { 407 features.add(feature); 408 renewEntityCapsVersion(); 409 } 410 } 411 412 /** 413 * Removes the specified feature from the supported features by this XMPP entity.<p> 414 * 415 * Since no packet is actually sent to the server it is safe to perform this operation 416 * before logging to the server. 417 * 418 * @param feature the feature to remove from the supported features. 419 */ 420 public void removeFeature(String feature) { 421 synchronized (features) { 422 features.remove(feature); 423 renewEntityCapsVersion(); 424 } 425 } 426 427 /** 428 * Returns true if the specified feature is registered in the ServiceDiscoveryManager. 429 * 430 * @param feature the feature to look for. 431 * @return a boolean indicating if the specified featured is registered or not. 432 */ 433 public boolean includesFeature(String feature) { 434 synchronized (features) { 435 return features.contains(feature); 436 } 437 } 438 439 /** 440 * Registers extended discovery information of this XMPP entity. When this 441 * client is queried for its information this data form will be returned as 442 * specified by XEP-0128. 443 * <p> 444 * 445 * Since no packet is actually sent to the server it is safe to perform this 446 * operation before logging to the server. In fact, you may want to 447 * configure the extended info before logging to the server so that the 448 * information is already available if it is required upon login. 449 * 450 * @param info 451 * the data form that contains the extend service discovery 452 * information. 453 */ 454 public void setExtendedInfo(DataForm info) { 455 extendedInfo = info; 456 renewEntityCapsVersion(); 457 } 458 459 /** 460 * Returns the data form that is set as extended information for this Service Discovery instance (XEP-0128) 461 * 462 * @see <a href="http://xmpp.org/extensions/xep-0128.html">XEP-128: Service Discovery Extensions</a> 463 * @return the data form 464 */ 465 public DataForm getExtendedInfo() { 466 return extendedInfo; 467 } 468 469 /** 470 * Returns the data form as List of PacketExtensions, or null if no data form is set. 471 * This representation is needed by some classes (e.g. EntityCapsManager, NodeInformationProvider) 472 * 473 * @return the data form as List of PacketExtensions 474 */ 475 public List<PacketExtension> getExtendedInfoAsList() { 476 List<PacketExtension> res = null; 477 if (extendedInfo != null) { 478 res = new ArrayList<PacketExtension>(1); 479 res.add(extendedInfo); 480 } 481 return res; 482 } 483 484 /** 485 * Removes the data form containing extended service discovery information 486 * from the information returned by this XMPP entity.<p> 487 * 488 * Since no packet is actually sent to the server it is safe to perform this 489 * operation before logging to the server. 490 */ 491 public void removeExtendedInfo() { 492 extendedInfo = null; 493 renewEntityCapsVersion(); 494 } 495 496 /** 497 * Returns the discovered information of a given XMPP entity addressed by its JID. 498 * Use null as entityID to query the server 499 * 500 * @param entityID the address of the XMPP entity or null. 501 * @return the discovered information. 502 * @throws XMPPErrorException 503 * @throws NoResponseException 504 * @throws NotConnectedException 505 */ 506 public DiscoverInfo discoverInfo(String entityID) throws NoResponseException, XMPPErrorException, NotConnectedException { 507 if (entityID == null) 508 return discoverInfo(null, null); 509 510 // Check if the have it cached in the Entity Capabilities Manager 511 DiscoverInfo info = EntityCapsManager.getDiscoverInfoByUser(entityID); 512 513 if (info != null) { 514 // We were able to retrieve the information from Entity Caps and 515 // avoided a disco request, hurray! 516 return info; 517 } 518 519 // Try to get the newest node#version if it's known, otherwise null is 520 // returned 521 EntityCapsManager.NodeVerHash nvh = EntityCapsManager.getNodeVerHashByJid(entityID); 522 523 // Discover by requesting the information from the remote entity 524 // Note that wee need to use NodeVer as argument for Node if it exists 525 info = discoverInfo(entityID, nvh != null ? nvh.getNodeVer() : null); 526 527 // If the node version is known, store the new entry. 528 if (nvh != null) { 529 if (EntityCapsManager.verifyDiscoverInfoVersion(nvh.getVer(), nvh.getHash(), info)) 530 EntityCapsManager.addDiscoverInfoByNode(nvh.getNodeVer(), info); 531 } 532 533 return info; 534 } 535 536 /** 537 * Returns the discovered information of a given XMPP entity addressed by its JID and 538 * note attribute. Use this message only when trying to query information which is not 539 * directly addressable. 540 * 541 * @see <a href="http://xmpp.org/extensions/xep-0030.html#info-basic">XEP-30 Basic Protocol</a> 542 * @see <a href="http://xmpp.org/extensions/xep-0030.html#info-nodes">XEP-30 Info Nodes</a> 543 * 544 * @param entityID the address of the XMPP entity. 545 * @param node the optional attribute that supplements the 'jid' attribute. 546 * @return the discovered information. 547 * @throws XMPPErrorException if the operation failed for some reason. 548 * @throws NoResponseException if there was no response from the server. 549 * @throws NotConnectedException 550 */ 551 public DiscoverInfo discoverInfo(String entityID, String node) throws NoResponseException, XMPPErrorException, NotConnectedException { 552 // Discover the entity's info 553 DiscoverInfo disco = new DiscoverInfo(); 554 disco.setType(IQ.Type.GET); 555 disco.setTo(entityID); 556 disco.setNode(node); 557 558 Packet result = connection().createPacketCollectorAndSend(disco).nextResultOrThrow(); 559 560 return (DiscoverInfo) result; 561 } 562 563 /** 564 * Returns the discovered items of a given XMPP entity addressed by its JID. 565 * 566 * @param entityID the address of the XMPP entity. 567 * @return the discovered information. 568 * @throws XMPPErrorException if the operation failed for some reason. 569 * @throws NoResponseException if there was no response from the server. 570 * @throws NotConnectedException 571 */ 572 public DiscoverItems discoverItems(String entityID) throws NoResponseException, XMPPErrorException, NotConnectedException { 573 return discoverItems(entityID, null); 574 } 575 576 /** 577 * Returns the discovered items of a given XMPP entity addressed by its JID and 578 * note attribute. Use this message only when trying to query information which is not 579 * directly addressable. 580 * 581 * @param entityID the address of the XMPP entity. 582 * @param node the optional attribute that supplements the 'jid' attribute. 583 * @return the discovered items. 584 * @throws XMPPErrorException if the operation failed for some reason. 585 * @throws NoResponseException if there was no response from the server. 586 * @throws NotConnectedException 587 */ 588 public DiscoverItems discoverItems(String entityID, String node) throws NoResponseException, XMPPErrorException, NotConnectedException { 589 // Discover the entity's items 590 DiscoverItems disco = new DiscoverItems(); 591 disco.setType(IQ.Type.GET); 592 disco.setTo(entityID); 593 disco.setNode(node); 594 595 Packet result = connection().createPacketCollectorAndSend(disco).nextResultOrThrow(); 596 return (DiscoverItems) result; 597 } 598 599 /** 600 * Returns true if the server supports publishing of items. A client may wish to publish items 601 * to the server so that the server can provide items associated to the client. These items will 602 * be returned by the server whenever the server receives a disco request targeted to the bare 603 * address of the client (i.e. user@host.com). 604 * 605 * @param entityID the address of the XMPP entity. 606 * @return true if the server supports publishing of items. 607 * @throws XMPPErrorException 608 * @throws NoResponseException 609 * @throws NotConnectedException 610 */ 611 public boolean canPublishItems(String entityID) throws NoResponseException, XMPPErrorException, NotConnectedException { 612 DiscoverInfo info = discoverInfo(entityID); 613 return canPublishItems(info); 614 } 615 616 /** 617 * Returns true if the server supports publishing of items. A client may wish to publish items 618 * to the server so that the server can provide items associated to the client. These items will 619 * be returned by the server whenever the server receives a disco request targeted to the bare 620 * address of the client (i.e. user@host.com). 621 * 622 * @param info the discover info packet to check. 623 * @return true if the server supports publishing of items. 624 */ 625 public static boolean canPublishItems(DiscoverInfo info) { 626 return info.containsFeature("http://jabber.org/protocol/disco#publish"); 627 } 628 629 /** 630 * Publishes new items to a parent entity. The item elements to publish MUST have at least 631 * a 'jid' attribute specifying the Entity ID of the item, and an action attribute which 632 * specifies the action being taken for that item. Possible action values are: "update" and 633 * "remove". 634 * 635 * @param entityID the address of the XMPP entity. 636 * @param discoverItems the DiscoveryItems to publish. 637 * @throws XMPPErrorException 638 * @throws NoResponseException 639 * @throws NotConnectedException 640 */ 641 public void publishItems(String entityID, DiscoverItems discoverItems) throws NoResponseException, XMPPErrorException, NotConnectedException { 642 publishItems(entityID, null, discoverItems); 643 } 644 645 /** 646 * Publishes new items to a parent entity and node. The item elements to publish MUST have at 647 * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which 648 * specifies the action being taken for that item. Possible action values are: "update" and 649 * "remove". 650 * 651 * @param entityID the address of the XMPP entity. 652 * @param node the attribute that supplements the 'jid' attribute. 653 * @param discoverItems the DiscoveryItems to publish. 654 * @throws XMPPErrorException if the operation failed for some reason. 655 * @throws NoResponseException if there was no response from the server. 656 * @throws NotConnectedException 657 */ 658 public void publishItems(String entityID, String node, DiscoverItems discoverItems) throws NoResponseException, XMPPErrorException, NotConnectedException 659 { 660 discoverItems.setType(IQ.Type.SET); 661 discoverItems.setTo(entityID); 662 discoverItems.setNode(node); 663 664 connection().createPacketCollectorAndSend(discoverItems).nextResultOrThrow(); 665 } 666 667 /** 668 * Queries the remote entity for it's features and returns true if the given feature is found. 669 * 670 * @param jid the JID of the remote entity 671 * @param feature 672 * @return true if the entity supports the feature, false otherwise 673 * @throws XMPPErrorException 674 * @throws NoResponseException 675 * @throws NotConnectedException 676 */ 677 public boolean supportsFeature(String jid, String feature) throws NoResponseException, XMPPErrorException, NotConnectedException { 678 DiscoverInfo result = discoverInfo(jid); 679 return result.containsFeature(feature); 680 } 681 682 /** 683 * Entity Capabilities 684 */ 685 686 /** 687 * Loads the ServiceDiscoveryManager with an EntityCapsManger that speeds up certain lookups. 688 * 689 * @param manager 690 */ 691 public void setEntityCapsManager(EntityCapsManager manager) { 692 capsManager = manager; 693 } 694 695 /** 696 * Updates the Entity Capabilities Verification String if EntityCaps is enabled. 697 */ 698 private void renewEntityCapsVersion() { 699 if (capsManager != null && capsManager.entityCapsEnabled()) 700 capsManager.updateLocalEntityCaps(); 701 } 702}