@Override
    public IQ handleIQ(IQ packet) {
        if (packet.isResponse()) {
            // ignore
            return null;
        }
        // Create a copy of the sent pack that will be used as the reply
        // we only need to add the requested info to the reply if any otherwise add 
        // a not found error
        IQ reply = IQ.createResultIQ(packet);

        // Look for a DiscoInfoProvider associated with the requested entity.
        // We consider the host of the recipient JID of the packet as the entity. It's the 
        // DiscoInfoProvider responsibility to provide information about the JID's name together 
        // with any possible requested node.  
        DiscoInfoProvider infoProvider = getProvider(packet.getTo() == null ?
                XMPPServer.getInstance().getServerInfo().getXMPPDomain() : packet.getTo().getDomain());
        if (infoProvider != null) {
            // Get the JID's name
            String name = packet.getTo() == null ? null : packet.getTo().getNode();
            if (name == null || name.trim().isEmpty()) {
                name = null;
            }
            // Get the requested node
            Element iq = packet.getChildElement();
            String node = iq.attributeValue("node");
            //String node = metaData.getProperty("query:node");

            // Legacy implementation assumes that, when querying the server, the node is null.
            // This is not true for the XEP-0115 based requests for the server itself. As a
            // hack, the node value is considered 'null' when the request that's being handled
            // appears to be such a request.
            if ( node != null && node.startsWith( EntityCapabilitiesManager.OPENFIRE_IDENTIFIER_NODE + "#" ) ) {
                node = null;
            }

            try {
                // Check if we have information about the requested name and node
                if (infoProvider.hasInfo(name, node, packet.getFrom())) {
                    reply.setChildElement(iq.createCopy());
                    Element queryElement = reply.getChildElement();

                    // Add to the reply all the identities provided by the DiscoInfoProvider
                    Element identity;
                    Iterator<Element> identities = infoProvider.getIdentities(name, node, packet.getFrom());
                    while (identities.hasNext()) {
                        identity = identities.next();
                        identity.setQName(new QName(identity.getName(), queryElement.getNamespace()));
                        queryElement.add((Element) identity.clone());
                    }

                    // Add to the reply all the features provided by the DiscoInfoProvider
                    Iterator<String> features = infoProvider.getFeatures(name, node, packet.getFrom());
                    boolean hasDiscoInfoFeature = false;
                    boolean hasDiscoItemsFeature = false;
                    boolean hasResultSetManagementFeature = false;

                    while (features.hasNext()) {
                        final String feature = features.next();
                        queryElement.addElement("feature").addAttribute("var", feature);
                        if (feature.equals(NAMESPACE_DISCO_INFO)) {
                            hasDiscoInfoFeature = true;
                        } else if (feature.equals("http://jabber.org/protocol/disco#items")) {
                            hasDiscoItemsFeature = true;
                        } else if (feature.equals(ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT)) {
                            hasResultSetManagementFeature = true;
                        }
                    }

                    if (hasDiscoItemsFeature && !hasResultSetManagementFeature) {
                        // IQDiscoItemsHandler provides result set management
                        // support.
                        queryElement.addElement("feature").addAttribute("var",
                            ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT);
                    }

                    if (!hasDiscoInfoFeature) {
                        // XEP-0030 requires that every entity that supports service
                        // discovery broadcasts the disco#info feature.
                        queryElement.addElement("feature").addAttribute("var", NAMESPACE_DISCO_INFO);
                        // XEP-0411 requires that every entity that supports service
                        // dicovery broadcasts the conversion between 'PEP' and 'Private Storage' feature
                        if (XMPPServer.getInstance().getPrivateStorage().isEnabled()) {
                            //allow only if private storage is enabled
                            queryElement.addElement("feature").addAttribute("var", "urn:xmpp:bookmarks-conversion:0");
                        }
                    }
                    // Add to the reply the multiple extended info (XDataForm) provided by the DiscoInfoProvider
                    final Set<DataForm> dataForms = infoProvider.getExtendedInfos(name, node, packet.getFrom());
                    if (dataForms != null) {
                        dataForms.forEach(dataForm -> queryElement.add(dataForm.getElement()));
                    }
                } else {
                    // If the DiscoInfoProvider has no information for the requested name and node
                    // then answer a not found error
                    reply.setChildElement(packet.getChildElement().createCopy());
                    reply.setError(PacketError.Condition.item_not_found);
                }
            } catch (UnauthorizedException e) {
                Log.debug("Entity '{}' attempted to query '{}' for disco#info, but is not authorized.", packet.getFrom(), packet.getTo(), e);
                // XEP-0030, Section 8 "Security Considerations": <blockquote>The following rules apply to the handling of
                // service discovery requests sent to bare JIDs: In response to a disco#info request, the server MUST return
                // a <service-unavailable/> error if [...] The requesting entity is not authorized to receive presence from the
                // target entity (i.e., via the target having a presence subscription to the requesting entity of type "both" or
                // "from") or is not otherwise trusted (e.g., another server in a trusted network).</blockquote>
                reply.setChildElement(packet.getChildElement().createCopy());
                reply.setError(PacketError.Condition.service_unavailable);
            }
        }
        else {
            // If we didn't find a DiscoInfoProvider then answer a not found error
            reply.setChildElement(packet.getChildElement().createCopy());
            reply.setError(PacketError.Condition.item_not_found);
        }

        return reply;
    }
IQDiscoInfoHandler is responsible for handling disco#info requests. This class holds a map with
the main entities and the associated DiscoInfoProvider. We are considering the host of the
recipient JIDs as main entities. It's the DiscoInfoProvider responsibility to provide information
about the JID's name together with any possible requested node.
<p>
For example, let's have in the entities map the following entries: "localhost" and
"conference.localhost". Associated with each entry we have different DiscoInfoProviders. Now we
receive a disco#info request for the following JID: "room@conference.localhost" which is a disco
request for a MUC room. So IQDiscoInfoHandler will look for the DiscoInfoProvider associated
with the JID's host which in this case is "conference.localhost". Once we have located the
provider we will delegate to the provider the responsibility to provide the info specific to
the JID's name which in this case is "room". Among the information that a room could provide we
could find its identity and the features it supports (e.g. 'muc_passwordprotected',
'muc_unmoderated', etc.). Finally, after we have collected all the information provided by the
provider we will add it to the reply. On the other hand, if no provider was found or the provider
has no information for the requested name/node then a not-found error will be returned.</p>

@author Gaston Dombiak
xmppserver/src/main/java/org/jivesoftware/openfire/disco/IQDiscoInfoHandler.java