/**
   * Publishes new items to a parent entity and node. The item elements to publish MUST have at
   * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which
   * specifies the action being taken for that item. Possible action values are: "update" and
   * "remove".
   *
   * @param entityID the address of the XMPP entity.
   * @param node the attribute that supplements the 'jid' attribute.
   * @param discoverItems the DiscoveryItems to publish.
   * @throws XMPPErrorException if the operation failed for some reason.
   * @throws NoResponseException if there was no response from the server.
   * @throws NotConnectedException
   */
  public void publishItems(String entityID, String node, DiscoverItems discoverItems)
      throws NoResponseException, XMPPErrorException, NotConnectedException {
    discoverItems.setType(IQ.Type.set);
    discoverItems.setTo(entityID);
    discoverItems.setNode(node);

    connection().createPacketCollectorAndSend(discoverItems).nextResultOrThrow();
  }
  /**
   * Publishes new items to a parent entity and node. The item elements to publish MUST have at
   * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which
   * specifies the action being taken for that item. Possible action values are: "update" and
   * "remove".
   *
   * @param entityID the address of the XMPP entity.
   * @param node the attribute that supplements the 'jid' attribute.
   * @param discoverItems the DiscoveryItems to publish.
   * @throws XMPPException if the operation failed for some reason.
   */
  public void publishItems(String entityID, String node, DiscoverItems discoverItems)
      throws XMPPException {
    Connection connection = ServiceDiscoveryManager.this.connection.get();
    if (connection == null) throw new XMPPException("Connection instance already gc'ed");

    discoverItems.setType(IQ.Type.SET);
    discoverItems.setTo(entityID);
    discoverItems.setNode(node);

    connection.createPacketCollectorAndSend(discoverItems).nextResultOrThrow();
  }
  /**
   * Returns the discovered items of a given XMPP entity addressed by its JID and note attribute.
   * Use this message only when trying to query information which is not directly addressable.
   *
   * @param entityID the address of the XMPP entity.
   * @param node the optional attribute that supplements the 'jid' attribute.
   * @return the discovered items.
   * @throws XMPPErrorException if the operation failed for some reason.
   * @throws NoResponseException if there was no response from the server.
   * @throws NotConnectedException
   */
  public DiscoverItems discoverItems(String entityID, String node)
      throws NoResponseException, XMPPErrorException, NotConnectedException {
    // Discover the entity's items
    DiscoverItems disco = new DiscoverItems();
    disco.setType(IQ.Type.get);
    disco.setTo(entityID);
    disco.setNode(node);

    Packet result = connection().createPacketCollectorAndSend(disco).nextResultOrThrow();
    return (DiscoverItems) result;
  }
  /**
   * Returns the discovered items of a given XMPP entity addressed by its JID and note attribute.
   * Use this message only when trying to query information which is not directly addressable.
   *
   * @param entityID the address of the XMPP entity.
   * @param node the optional attribute that supplements the 'jid' attribute.
   * @return the discovered items.
   * @throws XMPPException if the operation failed for some reason.
   */
  public DiscoverItems discoverItems(String entityID, String node) throws XMPPException {
    Connection connection = ServiceDiscoveryManager.this.connection.get();
    if (connection == null) throw new XMPPException("Connection instance already gc'ed");

    // Discover the entity's items
    DiscoverItems disco = new DiscoverItems();
    disco.setType(IQ.Type.GET);
    disco.setTo(entityID);
    disco.setNode(node);

    Packet result = connection.createPacketCollectorAndSend(disco).nextResultOrThrow();
    return (DiscoverItems) result;
  }
  /**
   * Returns the address of the multiple recipients service. To obtain such address service
   * discovery is going to be used on the connected server and if none was found then another
   * attempt will be tried on the server items. The discovered information is going to be cached for
   * 24 hours.
   *
   * @param connection the connection to use for disco. The connected server is going to be queried.
   * @return the address of the multiple recipients service or <tt>null</tt> if none was found.
   */
  private static String getMultipleRecipienServiceAddress(Connection connection) {
    String serviceName = connection.getServiceName();
    String serviceAddress = (String) services.get(serviceName);
    if (serviceAddress == null) {
      synchronized (services) {
        serviceAddress = (String) services.get(serviceName);
        if (serviceAddress == null) {

          // Send the disco packet to the server itself
          try {
            DiscoverInfo info =
                ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(serviceName);
            // Check if the server supports JEP-33
            if (info.containsFeature("http://jabber.org/protocol/address")) {
              serviceAddress = serviceName;
            } else {
              // Get the disco items and send the disco packet to each server item
              DiscoverItems items =
                  ServiceDiscoveryManager.getInstanceFor(connection).discoverItems(serviceName);
              for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext(); ) {
                DiscoverItems.Item item = it.next();
                info =
                    ServiceDiscoveryManager.getInstanceFor(connection)
                        .discoverInfo(item.getEntityID(), item.getNode());
                if (info.containsFeature("http://jabber.org/protocol/address")) {
                  serviceAddress = serviceName;
                  break;
                }
              }
            }
            // Cache the discovered information
            services.put(serviceName, serviceAddress == null ? "" : serviceAddress);
          } catch (XMPPException e) {
            LOGGER.log(Level.SEVERE, "Error occurred retrieving multiple recipients service", e);
          }
        }
      }
    }

    return "".equals(serviceAddress) ? null : serviceAddress;
  }
 /**
  * Find all services under the users service that provide a given feature.
  *
  * @param feature the feature to search for
  * @param stopOnFirst if true, stop searching after the first service was found
  * @param useCache if true, query a cache first to avoid network I/O
  * @return a possible empty list of services providing the given feature
  * @throws NoResponseException
  * @throws XMPPErrorException
  * @throws NotConnectedException
  */
 public List<String> findServices(String feature, boolean stopOnFirst, boolean useCache)
     throws NoResponseException, XMPPErrorException, NotConnectedException {
   List<String> serviceAddresses = null;
   String serviceName = connection().getServiceName();
   if (useCache) {
     serviceAddresses = (List<String>) services.get(feature);
     if (serviceAddresses != null) {
       return serviceAddresses;
     }
   }
   serviceAddresses = new LinkedList<String>();
   // Send the disco packet to the server itself
   DiscoverInfo info;
   try {
     info = discoverInfo(serviceName);
   } catch (XMPPErrorException e) {
     // Be extra robust here: Return the empty linked list and log this situation
     LOGGER.log(Level.WARNING, "Could not discover information about service", e);
     return serviceAddresses;
   }
   // Check if the server supports XEP-33
   if (info.containsFeature(feature)) {
     serviceAddresses.add(serviceName);
     if (stopOnFirst) {
       if (useCache) {
         // Cache the discovered information
         services.put(feature, serviceAddresses);
       }
       return serviceAddresses;
     }
   }
   DiscoverItems items;
   try {
     // Get the disco items and send the disco packet to each server item
     items = discoverItems(serviceName);
   } catch (XMPPErrorException e) {
     LOGGER.log(Level.WARNING, "Could not discover items about service", e);
     return serviceAddresses;
   }
   for (DiscoverItems.Item item : items.getItems()) {
     try {
       // TODO is it OK here in all cases to query without the node attribute?
       // MultipleRecipientManager queried initially also with the node attribute, but this
       // could be simply a fault instead of intentional.
       info = discoverInfo(item.getEntityID());
     } catch (XMPPErrorException | NoResponseException e) {
       // Don't throw this exceptions if one of the server's items fail
       LOGGER.log(
           Level.WARNING,
           "Exception while discovering info for feature "
               + feature
               + " of "
               + item.getEntityID()
               + " node: "
               + item.getNode(),
           e);
       continue;
     }
     if (info.containsFeature(feature)) {
       serviceAddresses.add(item.getEntityID());
       if (stopOnFirst) {
         break;
       }
     }
   }
   if (useCache) {
     // Cache the discovered information
     services.put(feature, serviceAddresses);
   }
   return serviceAddresses;
 }