예제 #1
0
  /**
   * Add a record telling what entity caps node a user has.
   *
   * @param user the user (Full JID)
   * @param node the node (of the caps packet extension)
   * @param hash the hashing algorithm used to calculate <tt>ver</tt>
   * @param ver the version (of the caps packet extension)
   * @param ext the ext (of the caps packet extension)
   * @param online indicates if the user is online
   */
  private void addUserCapsNode(
      String user, String node, String hash, String ver, String ext, boolean online) {
    if ((user != null) && (node != null) && (hash != null) && (ver != null)) {
      Caps caps = userCaps.get(user);

      if ((caps == null)
          || !caps.node.equals(node)
          || !caps.hash.equals(hash)
          || !caps.ver.equals(ver)) {
        caps = new Caps(node, hash, ver, ext);

        userCaps.put(user, caps);
      } else return;

      // Fire userCapsNodeAdded.
      UserCapsNodeListener[] listeners;

      synchronized (userCapsNodeListeners) {
        listeners = userCapsNodeListeners.toArray(NO_USER_CAPS_NODE_LISTENERS);
      }
      if (listeners.length != 0) {
        String nodeVer = caps.getNodeVer();

        for (UserCapsNodeListener listener : listeners)
          listener.userCapsNodeAdded(user, nodeVer, online);
      }
    }
  }
  @Override
  public void disconnected(boolean onError) {
    final XMPPConnection connection = myFacade.getConnection();

    LOG.info("Jabber disconnected: " + connection.getUser());
    connection.removePacketListener(mySubscribeListener);
    mySubscribeListener = null;
    connection.removePacketListener(myMessageListener);
    myMessageListener = null;

    final Roster roster = connection.getRoster();
    if (roster != null) {
      roster.removeRosterListener(myRosterListener);
    }
    myRosterListener = null;

    myIDEtalkUsers.clear();
    myUser2Presence.clear();
    myUser2Thread.clear();

    if (onError && reconnectEnabledAndNotStarted()) {
      LOG.warn(getMsg("jabber.server.was.disconnected", myReconnectTimeout / 1000));
      myReconnectProcess = myIdeFacade.runOnPooledThread(new MyReconnectRunnable());
    }
  }
예제 #3
0
  /**
   * Returns the discovered information of a given XMPP entity addressed by its JID.
   *
   * @param entityID the address of the XMPP entity.
   * @return the discovered information.
   * @throws XMPPException if the operation failed for some reason.
   */
  public DiscoverInfo discoverInfo(String entityID) throws XMPPException {
    // Check if the have it cached in the Entity Capabilities Manager
    DiscoverInfo info = discoverInfoByCaps(entityID);

    if (info != null) {
      return info;
    } else {
      // If the caps node is known, use it in the request.
      String node = null;

      if (capsManager != null) {
        // Get the newest node#version
        node = capsManager.getNodeVersionByUser(entityID);
      }

      // Check if we cached DiscoverInfo for nonCaps entity
      if (cacheNonCaps && node == null && nonCapsCache.containsKey(entityID)) {
        return nonCapsCache.get(entityID);
      }
      // Discover by requesting from the remote client
      info = discoverInfo(entityID, node);

      // If the node version is known, store the new entry.
      if (node != null && capsManager != null) {
        EntityCapsManager.addDiscoverInfoByNode(node, info);
      }
      // If this is a non caps entity store the discover in nonCapsCache map
      else if (cacheNonCaps && node == null) {
        nonCapsCache.put(entityID, info);
      }
      return info;
    }
  }
예제 #4
0
  /**
   * Remove records telling what entity caps node a contact has.
   *
   * @param contact the contact
   */
  public void removeContactCapsNode(Contact contact) {
    Caps caps = null;
    String lastRemovedJid = null;

    Iterator<String> iter = userCaps.keySet().iterator();
    while (iter.hasNext()) {
      String jid = iter.next();

      if (StringUtils.parseBareAddress(jid).equals(contact.getAddress())) {
        caps = userCaps.get(jid);
        lastRemovedJid = jid;
        iter.remove();
      }
    }

    // fire only for the last one, at the end the event out
    // of the protocol will be one and for the contact
    if (caps != null) {
      UserCapsNodeListener[] listeners;
      synchronized (userCapsNodeListeners) {
        listeners = userCapsNodeListeners.toArray(NO_USER_CAPS_NODE_LISTENERS);
      }
      if (listeners.length != 0) {
        String nodeVer = caps.getNodeVer();

        for (UserCapsNodeListener listener : listeners)
          listener.userCapsNodeRemoved(lastRemovedJid, nodeVer, false);
      }
    }
  }
 String getThreadId(User user) {
   String id = myUser2Thread.get(user.getName());
   if (id == null) {
     id = myThreadIdPrefix + myCurrentThreadId++;
     myUser2Thread.put(user.getName(), id);
   }
   return id;
 }
 @Override
 public UserPresence getUserPresence(User user) {
   UserPresence presence = myUser2Presence.get(user);
   if (presence == null) {
     presence = new UserPresence(false);
     myUser2Presence.put(user, presence);
   }
   return presence;
 }
 /**
  * Returns the ServiceDiscoveryManager instance associated with a given Connection.
  *
  * @param connection the connection used to look for the proper ServiceDiscoveryManager.
  * @return the ServiceDiscoveryManager associated with a given Connection.
  */
 public static synchronized ServiceDiscoveryManager getInstanceFor(Connection connection) {
   ServiceDiscoveryManager sdm = instances.get(connection);
   if (sdm == null) {
     sdm = new ServiceDiscoveryManager(connection);
   }
   return sdm;
 }
예제 #8
0
  /**
   * Add {@link DiscoverInfo} to our caps database.
   *
   * <p><b>Warning</b>: The specified <tt>DiscoverInfo</tt> is trusted to be valid with respect to
   * the specified <tt>Caps</tt> for performance reasons because the <tt>DiscoverInfo</tt> should
   * have already been validated in order to be used elsewhere anyway.
   *
   * @param caps the <tt>Caps<tt/> i.e. the node, the hash and the ver for which a
   *     <tt>DiscoverInfo</tt> is to be added to our caps database.
   * @param info {@link DiscoverInfo} for the specified <tt>Caps</tt>.
   */
  public static void addDiscoverInfoByCaps(Caps caps, DiscoverInfo info) {
    cleanupDiscoverInfo(info);
    /*
     * DiscoverInfo carries the node we're now associating it with a
     * specific node so we'd better keep them in sync.
     */
    info.setNode(caps.getNodeVer());

    synchronized (caps2discoverInfo) {
      DiscoverInfo oldInfo = caps2discoverInfo.put(caps, info);

      /*
       * If the specified info is a new association for the specified
       * node, remember it across application instances in order to not
       * query for it over the network.
       */
      if ((oldInfo == null) || !oldInfo.equals(info)) {
        String xml = info.getChildElementXML();

        if ((xml != null) && (xml.length() != 0)) {
          getConfigService().setProperty(getCapsPropertyName(caps), xml);
        }
      }
    }
  }
  /**
   * Maps the specified <tt>address</tt> to <tt>jid</tt>. The point of this method is to allow us to
   * send all messages destined to the contact with the specified <tt>address</tt> to the
   * <tt>jid</tt> that they last contacted us from.
   *
   * @param threadID the threadID of conversation.
   * @param jid the jid (i.e. address/resource) that the contact with the specified <tt>address</tt>
   *     last contacted us from.
   */
  private void putJidForAddress(String jid, String threadID) {
    synchronized (jids) {
      purgeOldJids();

      StoredThreadID ta = jids.get(jid);

      if (ta == null) {
        ta = new StoredThreadID();
        jids.put(jid, ta);
      }

      recentJIDForAddress.put(StringUtils.parseBareAddress(jid), jid);

      ta.lastUpdatedTime = System.currentTimeMillis();
      ta.threadID = threadID;
    }
  }
  /**
   * Returns the last jid that the party with the specified <tt>address</tt> contacted us from or
   * <tt>null</tt>(or bare jid) if we don't have a jid for the specified <tt>address</tt> yet. The
   * method would also purge all entries that haven't seen any activity (i.e. no one has tried to
   * get or remap it) for a delay longer than <tt>JID_INACTIVITY_TIMEOUT</tt>.
   *
   * @param jid the <tt>jid</tt> that we'd like to obtain a threadID for.
   * @return the last jid that the party with the specified <tt>address</tt> contacted us from or
   *     <tt>null</tt> if we don't have a jid for the specified <tt>address</tt> yet.
   */
  String getThreadIDForAddress(String jid) {
    synchronized (jids) {
      purgeOldJids();
      StoredThreadID ta = jids.get(jid);

      if (ta == null) return null;

      ta.lastUpdatedTime = System.currentTimeMillis();

      return ta.threadID;
    }
  }
  /**
   * Remove from our <tt>jids</tt> map all entries that have not seen any activity (i.e. neither
   * outgoing nor incoming messags) for more than JID_INACTIVITY_TIMEOUT. Note that this method is
   * not synchronous and that it is only meant for use by the {@link #getThreadIDForAddress(String)}
   * and {@link #putJidForAddress(String, String)}
   */
  private void purgeOldJids() {
    long currentTime = System.currentTimeMillis();

    Iterator<Map.Entry<String, StoredThreadID>> entries = jids.entrySet().iterator();

    while (entries.hasNext()) {
      Map.Entry<String, StoredThreadID> entry = entries.next();
      StoredThreadID target = entry.getValue();

      if (currentTime - target.lastUpdatedTime > JID_INACTIVITY_TIMEOUT) entries.remove();
    }
  }
예제 #12
0
  /** 发送消息 */
  public void sendMessage(String sessionJID, String sessionName, String message, String type)
      throws RemoteException {
    ChatManager chatManager = ChatManager.getInstanceFor(connection);
    Chat chat;
    // 查找Chat对策
    if (jidChats.containsKey(sessionJID)) {
      chat = jidChats.get(sessionJID);
      // 创建Chat
    } else {
      chat = chatManager.createChat(sessionJID, null);
      // 添加到集合
      jidChats.put(sessionJID, chat);
    }

    if (chat != null) {
      try {
        // 发送消息
        chat.sendMessage(message);

        // 保存聊天记录
        ContentValues values = new ContentValues();
        values.put(SMSProvider.SMSColumns.BODY, message);
        values.put(SMSProvider.SMSColumns.TYPE, type);
        values.put(SMSProvider.SMSColumns.TIME, System.currentTimeMillis());

        values.put(SMSProvider.SMSColumns.WHO_ID, IM.getString(IM.ACCOUNT_JID));

        values.put(SMSProvider.SMSColumns.SESSION_ID, sessionJID);
        values.put(SMSProvider.SMSColumns.SESSION_NAME, sessionName);

        imService.getContentResolver().insert(SMSProvider.SMS_URI, values);

      } catch (XMPPException e) {
        e.printStackTrace();
      } catch (SmackException.NotConnectedException e) {
        e.printStackTrace();
      }
    }
  }
  private void updateUserPresence(String jabberId) {
    LOG.debug("Presence changed for " + jabberId);
    final User user = myUserModel.findUser(getSimpleId(jabberId), getName());
    if (user != null) {
      updateIsIDEtalkClient(jabberId, user);

      final UserPresence presence = _getUserPresence(user);
      IDEtalkEvent event = createPresenceChangeEvent(user, presence);
      if (event != null) {
        getBroadcaster().doChange(event, () -> myUser2Presence.put(user, presence));
      }
    }
  }
  /**
   * Determines whether the protocol supports the supplied content type for the given contact.
   *
   * @param contentType the type we want to check
   * @param contact contact which is checked for supported contentType
   * @return <tt>true</tt> if the contact supports it and <tt>false</tt> otherwise.
   */
  @Override
  public boolean isContentTypeSupported(String contentType, Contact contact) {
    // by default we support default mime type, for other mimetypes
    // method must be overriden
    if (contentType.equals(DEFAULT_MIME_TYPE)) return true;
    else if (contentType.equals(HTML_MIME_TYPE)) {
      String toJID = recentJIDForAddress.get(contact.getAddress());

      if (toJID == null) toJID = contact.getAddress();

      return jabberProvider.isFeatureListSupported(toJID, HTML_NAMESPACE);
    }

    return false;
  }
예제 #15
0
  /**
   * Remove a record telling what entity caps node a user has.
   *
   * @param user the user (Full JID)
   */
  public void removeUserCapsNode(String user) {
    Caps caps = userCaps.remove(user);

    // Fire userCapsNodeRemoved.
    if (caps != null) {
      UserCapsNodeListener[] listeners;

      synchronized (userCapsNodeListeners) {
        listeners = userCapsNodeListeners.toArray(NO_USER_CAPS_NODE_LISTENERS);
      }
      if (listeners.length != 0) {
        String nodeVer = caps.getNodeVer();

        for (UserCapsNodeListener listener : listeners)
          listener.userCapsNodeRemoved(user, nodeVer, false);
      }
    }
  }
 public String getRecentJIDForAddress(String address) {
   return recentJIDForAddress.get(address);
 }
예제 #17
0
 /**
  * Gets the <tt>Caps</tt> i.e. the node, the hash and the ver of a user.
  *
  * @param user the user (Full JID)
  * @return the <tt>Caps</tt> i.e. the node, the hash and the ver of <tt>user</tt>
  */
 public Caps getCapsByUser(String user) {
   return userCaps.get(user);
 }
예제 #18
0
  /**
   * Get the discover info given a user name. The discover info is returned if the user has a
   * node#ver associated with it and the node#ver has a discover info associated with it.
   *
   * @param user user name (Full JID)
   * @return the discovered info
   */
  public DiscoverInfo getDiscoverInfoByUser(String user) {
    Caps caps = userCaps.get(user);

    return (caps == null) ? null : getDiscoverInfoByCaps(caps);
  }
예제 #19
0
  /**
   * Retrieve DiscoverInfo for a specific node.
   *
   * @param caps the <tt>Caps</tt> i.e. the node, the hash and the ver
   * @return The corresponding DiscoverInfo or null if none is known.
   */
  public static DiscoverInfo getDiscoverInfoByCaps(Caps caps) {
    synchronized (caps2discoverInfo) {
      DiscoverInfo discoverInfo = caps2discoverInfo.get(caps);

      /*
       * If we don't have the discoverInfo in the runtime cache yet, we
       * may have it remembered in a previous application instance.
       */
      if (discoverInfo == null) {
        ConfigurationService configurationService = getConfigService();
        String capsPropertyName = getCapsPropertyName(caps);
        String xml = configurationService.getString(capsPropertyName);

        if ((xml != null) && (xml.length() != 0)) {
          IQProvider discoverInfoProvider =
              (IQProvider)
                  ProviderManager.getInstance()
                      .getIQProvider("query", "http://jabber.org/protocol/disco#info");

          if (discoverInfoProvider != null) {
            XmlPullParser parser = new MXParser();

            try {
              parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
              parser.setInput(new StringReader(xml));
              // Start the parser.
              parser.next();
            } catch (XmlPullParserException xppex) {
              parser = null;
            } catch (IOException ioex) {
              parser = null;
            }

            if (parser != null) {
              try {
                discoverInfo = (DiscoverInfo) discoverInfoProvider.parseIQ(parser);
              } catch (Exception ex) {
              }

              if (discoverInfo != null) {
                if (caps.isValid(discoverInfo)) caps2discoverInfo.put(caps, discoverInfo);
                else {
                  logger.error(
                      "Invalid DiscoverInfo for " + caps.getNodeVer() + ": " + discoverInfo);
                  /*
                   * The discoverInfo doesn't seem valid
                   * according to the caps which means that we
                   * must have stored invalid information.
                   * Delete the invalid information in order
                   * to not try to validate it again.
                   */
                  configurationService.removeProperty(capsPropertyName);
                }
              }
            }
          }
        }
      }
      return discoverInfo;
    }
  }
예제 #20
0
 /**
  * Removes the NodeInformationProvider responsible for providing information (ie items) related to
  * a given node. This means that no more information will be available for the specified node.
  *
  * <p>In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
  * NodeInformationProvider will provide information about the rooms where the user has joined.
  *
  * @param node the node to remove the associated NodeInformationProvider.
  */
 public void removeNodeInformationProvider(String node) {
   nodeInformationProviders.remove(node);
 }
예제 #21
0
 /**
  * Sets the NodeInformationProvider responsible for providing information (ie items) related to a
  * given node. Every time this client receives a disco request regarding the items of a given
  * node, the provider associated to that node will be the responsible for providing the requested
  * information.
  *
  * <p>In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
  * NodeInformationProvider will provide information about the rooms where the user has joined.
  *
  * @param node the node whose items will be provided by the NodeInformationProvider.
  * @param listener the NodeInformationProvider responsible for providing items related to the
  *     node.
  */
 public void setNodeInformationProvider(String node, NodeInformationProvider listener) {
   nodeInformationProviders.put(node, listener);
 }
예제 #22
0
 /**
  * Returns the NodeInformationProvider responsible for providing information (ie items) related to
  * a given node or <tt>null</null> if none.
  *
  * <p>In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the
  * NodeInformationProvider will provide information about the rooms where the user has joined.
  *
  * @param node the node that contains items associated with an entity not addressable as a JID.
  * @return the NodeInformationProvider responsible for providing information related to a given
  *     node.
  */
 private NodeInformationProvider getNodeInformationProvider(String node) {
   if (node == null) {
     return null;
   }
   return nodeInformationProviders.get(node);
 }
예제 #23
0
  /**
   * Initializes the packet listeners of the connection that will answer to any service discovery
   * request.
   */
  private void init() {
    // Register the new instance and associate it with the connection
    instances.put(connection, this);
    // Add a listener to the connection that removes the registered instance when
    // the connection is closed
    connection.addConnectionListener(
        new ConnectionListener() {
          public void connectionClosed() {
            // Unregister this instance since the connection has been closed
            instances.remove(connection);
          }

          public void connectionClosedOnError(Exception e) {
            // ignore
          }

          public void reconnectionFailed(Exception e) {
            // ignore
          }

          public void reconnectingIn(int seconds) {
            // ignore
          }

          public void reconnectionSuccessful() {
            // ignore
          }
        });

    // Intercept presence packages and add caps data when inteded.
    // XEP-0115 specifies that a client SHOULD include entity capabilities
    // with every presence notification it sends.
    PacketFilter capsPacketFilter = new PacketTypeFilter(Presence.class);
    PacketInterceptor packetInterceptor =
        new PacketInterceptor() {
          public void interceptPacket(Packet packet) {
            if (capsManager != null) {
              String ver = getEntityCapsVersion();
              CapsExtension caps = new CapsExtension(capsManager.getNode(), ver, "sha-1");
              packet.addExtension(caps);
            }
          }
        };
    connection.addPacketInterceptor(packetInterceptor, capsPacketFilter);

    // Listen for disco#items requests and answer with an empty result
    PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class);
    PacketListener packetListener =
        new PacketListener() {
          public void processPacket(Packet packet) {
            DiscoverItems discoverItems = (DiscoverItems) packet;
            // Send back the items defined in the client if the request is of type GET
            if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) {
              DiscoverItems response = new DiscoverItems();
              response.setType(IQ.Type.RESULT);
              response.setTo(discoverItems.getFrom());
              response.setPacketID(discoverItems.getPacketID());
              response.setNode(discoverItems.getNode());

              // Add the defined items related to the requested node. Look for
              // the NodeInformationProvider associated with the requested node.
              NodeInformationProvider nodeInformationProvider =
                  getNodeInformationProvider(discoverItems.getNode());
              if (nodeInformationProvider != null) {
                // Specified node was found
                List<DiscoverItems.Item> items = nodeInformationProvider.getNodeItems();
                if (items != null) {
                  for (DiscoverItems.Item item : items) {
                    response.addItem(item);
                  }
                }
              } else if (discoverItems.getNode() != null) {
                // Return <item-not-found/> error since client doesn't contain
                // the specified node
                response.setType(IQ.Type.ERROR);
                response.setError(new XMPPError(XMPPError.Condition.item_not_found));
              }
              connection.sendPacket(response);
            }
          }
        };
    connection.addPacketListener(packetListener, packetFilter);

    // Listen for disco#info requests and answer the client's supported features
    // To add a new feature as supported use the #addFeature message
    packetFilter = new PacketTypeFilter(DiscoverInfo.class);
    packetListener =
        new PacketListener() {
          public void processPacket(Packet packet) {
            DiscoverInfo discoverInfo = (DiscoverInfo) packet;
            // Answer the client's supported features if the request is of the GET type
            if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) {
              DiscoverInfo response = new DiscoverInfo();
              response.setType(IQ.Type.RESULT);
              response.setTo(discoverInfo.getFrom());
              response.setPacketID(discoverInfo.getPacketID());
              response.setNode(discoverInfo.getNode());
              // Add the client's identity and features if "node" is
              // null or our entity caps version.
              if (discoverInfo.getNode() == null
                  || (capsManager == null
                      ? true
                      : (capsManager.getNode() + "#" + getEntityCapsVersion())
                          .equals(discoverInfo.getNode()))) {
                addDiscoverInfoTo(response);
              } else {
                // Disco#info was sent to a node. Check if we have information of the
                // specified node
                NodeInformationProvider nodeInformationProvider =
                    getNodeInformationProvider(discoverInfo.getNode());
                if (nodeInformationProvider != null) {
                  // Node was found. Add node features
                  List<String> features = nodeInformationProvider.getNodeFeatures();
                  if (features != null) {
                    for (String feature : features) {
                      response.addFeature(feature);
                    }
                  }
                  // Add node identities
                  List<DiscoverInfo.Identity> identities =
                      nodeInformationProvider.getNodeIdentities();
                  if (identities != null) {
                    for (DiscoverInfo.Identity identity : identities) {
                      response.addIdentity(identity);
                    }
                  }
                } else {
                  // Return <item-not-found/> error since specified node was not found
                  response.setType(IQ.Type.ERROR);
                  response.setError(new XMPPError(XMPPError.Condition.item_not_found));
                }
              }
              connection.sendPacket(response);
            }
          }
        };
    connection.addPacketListener(packetListener, packetFilter);
  }
예제 #24
0
 /**
  * Returns the ServiceDiscoveryManager instance associated with a given connection.
  *
  * @param connection the connection used to look for the proper ServiceDiscoveryManager.
  * @return the ServiceDiscoveryManager associated with a given connection.
  */
 public static ServiceDiscoveryManager getInstanceFor(Connection connection) {
   return instances.get(connection);
 }
  /**
   * Creates a new ServiceDiscoveryManager for a given Connection. This means that the service
   * manager will respond to any service discovery request that the connection may receive.
   *
   * @param connection the connection to which a ServiceDiscoveryManager is going to be created.
   */
  private ServiceDiscoveryManager(Connection connection) {
    this.connection = new WeakReference<Connection>(connection);

    // Register the new instance and associate it with the connection
    instances.put(connection, this);

    addFeature(DiscoverInfo.NAMESPACE);
    addFeature(DiscoverItems.NAMESPACE);

    // Listen for disco#items requests and answer with an empty result
    PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class);
    PacketListener packetListener =
        new PacketListener() {
          public void processPacket(Packet packet) {
            Connection connection = ServiceDiscoveryManager.this.connection.get();
            if (connection == null) return;
            DiscoverItems discoverItems = (DiscoverItems) packet;
            // Send back the items defined in the client if the request is of type GET
            if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) {
              DiscoverItems response = new DiscoverItems();
              response.setType(IQ.Type.RESULT);
              response.setTo(discoverItems.getFrom());
              response.setPacketID(discoverItems.getPacketID());
              response.setNode(discoverItems.getNode());

              // Add the defined items related to the requested node. Look for
              // the NodeInformationProvider associated with the requested node.
              NodeInformationProvider nodeInformationProvider =
                  getNodeInformationProvider(discoverItems.getNode());
              if (nodeInformationProvider != null) {
                // Specified node was found, add node items
                response.addItems(nodeInformationProvider.getNodeItems());
                // Add packet extensions
                response.addExtensions(nodeInformationProvider.getNodePacketExtensions());
              } else if (discoverItems.getNode() != null) {
                // Return <item-not-found/> error since client doesn't contain
                // the specified node
                response.setType(IQ.Type.ERROR);
                response.setError(new XMPPError(XMPPError.Condition.item_not_found));
              }
              connection.sendPacket(response);
            }
          }
        };
    connection.addPacketListener(packetListener, packetFilter);

    // Listen for disco#info requests and answer the client's supported features
    // To add a new feature as supported use the #addFeature message
    packetFilter = new PacketTypeFilter(DiscoverInfo.class);
    packetListener =
        new PacketListener() {
          public void processPacket(Packet packet) {
            Connection connection = ServiceDiscoveryManager.this.connection.get();
            if (connection == null) return;
            DiscoverInfo discoverInfo = (DiscoverInfo) packet;
            // Answer the client's supported features if the request is of the GET type
            if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) {
              DiscoverInfo response = new DiscoverInfo();
              response.setType(IQ.Type.RESULT);
              response.setTo(discoverInfo.getFrom());
              response.setPacketID(discoverInfo.getPacketID());
              response.setNode(discoverInfo.getNode());
              // Add the client's identity and features only if "node" is null
              // and if the request was not send to a node. If Entity Caps are
              // enabled the client's identity and features are may also added
              // if the right node is chosen
              if (discoverInfo.getNode() == null) {
                addDiscoverInfoTo(response);
              } else {
                // Disco#info was sent to a node. Check if we have information of the
                // specified node
                NodeInformationProvider nodeInformationProvider =
                    getNodeInformationProvider(discoverInfo.getNode());
                if (nodeInformationProvider != null) {
                  // Node was found. Add node features
                  response.addFeatures(nodeInformationProvider.getNodeFeatures());
                  // Add node identities
                  response.addIdentities(nodeInformationProvider.getNodeIdentities());
                  // Add packet extensions
                  response.addExtensions(nodeInformationProvider.getNodePacketExtensions());
                } else {
                  // Return <item-not-found/> error since specified node was not found
                  response.setType(IQ.Type.ERROR);
                  response.setError(new XMPPError(XMPPError.Condition.item_not_found));
                }
              }
              connection.sendPacket(response);
            }
          }
        };
    connection.addPacketListener(packetListener, packetFilter);
  }
  /**
   * Initializes the packet listeners of the connection that will answer to any service discovery
   * request.
   */
  private void init() {
    // Register the new instance and associate it with the connection
    instances.put(connection, this);
    // Add a listener to the connection that removes the registered instance when
    // the connection is closed
    connection.addConnectionListener(
        new ConnectionListener() {
          public void connectionClosed() {
            // Unregister this instance since the connection has been closed
            instances.remove(connection);
          }

          public void connectionClosedOnError(Exception e) {
            // ignore
          }

          public void reconnectionFailed(Exception e) {
            // ignore
          }

          public void reconnectingIn(int seconds) {
            // ignore
          }

          public void reconnectionSuccessful() {
            // ignore
          }
        });

    // Listen for disco#items requests and answer with an empty result
    PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class);
    PacketListener packetListener =
        new PacketListener() {
          public void processPacket(Packet packet) {
            DiscoverItems discoverItems = (DiscoverItems) packet;
            // Send back the items defined in the client if the request is of type GET
            if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) {
              DiscoverItems response = new DiscoverItems();
              response.setType(IQ.Type.RESULT);
              response.setTo(discoverItems.getFrom());
              response.setPacketID(discoverItems.getPacketID());
              response.setNode(discoverItems.getNode());

              // Add the defined items related to the requested node. Look for
              // the NodeInformationProvider associated with the requested node.
              NodeInformationProvider nodeInformationProvider =
                  getNodeInformationProvider(discoverItems.getNode());
              if (nodeInformationProvider != null) {
                // Specified node was found
                List<DiscoverItems.Item> items = nodeInformationProvider.getNodeItems();
                if (items != null) {
                  for (DiscoverItems.Item item : items) {
                    response.addItem(item);
                  }
                }
              } else if (discoverItems.getNode() != null) {
                // Return <item-not-found/> error since client doesn't contain
                // the specified node
                response.setType(IQ.Type.ERROR);
                response.setError(new XMPPError(XMPPError.Condition.item_not_found));
              }
              connection.sendPacket(response);
            }
          }
        };
    connection.addPacketListener(packetListener, packetFilter);

    // Listen for disco#info requests and answer the client's supported features
    // To add a new feature as supported use the #addFeature message
    packetFilter = new PacketTypeFilter(DiscoverInfo.class);
    packetListener =
        new PacketListener() {
          public void processPacket(Packet packet) {
            DiscoverInfo discoverInfo = (DiscoverInfo) packet;
            // Answer the client's supported features if the request is of the GET type
            if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) {
              DiscoverInfo response = new DiscoverInfo();
              response.setType(IQ.Type.RESULT);
              response.setTo(discoverInfo.getFrom());
              response.setPacketID(discoverInfo.getPacketID());
              response.setNode(discoverInfo.getNode());
              // Add the client's identity and features only if "node" is null
              if (discoverInfo.getNode() == null) {
                // Set this client identity
                DiscoverInfo.Identity identity =
                    new DiscoverInfo.Identity("client", getIdentityName());
                identity.setType(getIdentityType());
                response.addIdentity(identity);
                // Add the registered features to the response
                synchronized (features) {
                  for (Iterator<String> it = getFeatures(); it.hasNext(); ) {
                    response.addFeature(it.next());
                  }
                }
              } else {
                // Disco#info was sent to a node. Check if we have information of the
                // specified node
                NodeInformationProvider nodeInformationProvider =
                    getNodeInformationProvider(discoverInfo.getNode());
                if (nodeInformationProvider != null) {
                  // Node was found. Add node features
                  List<String> features = nodeInformationProvider.getNodeFeatures();
                  if (features != null) {
                    for (String feature : features) {
                      response.addFeature(feature);
                    }
                  }
                  // Add node identities
                  List<DiscoverInfo.Identity> identities =
                      nodeInformationProvider.getNodeIdentities();
                  if (identities != null) {
                    for (DiscoverInfo.Identity identity : identities) {
                      response.addIdentity(identity);
                    }
                  }
                } else {
                  // Return <item-not-found/> error since specified node was not found
                  response.setType(IQ.Type.ERROR);
                  response.setError(new XMPPError(XMPPError.Condition.item_not_found));
                }
              }
              connection.sendPacket(response);
            }
          }
        };
    connection.addPacketListener(packetListener, packetFilter);
  }