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