public void process(Presence packet) {
        if (Presence.Type.unavailable == packet.getType()) {
            if (packet.getFrom() != null ) {
                final String oldVer;

                final Lock lock = this.entityCapabilitiesUserMap.getLock(packet.getFrom().asBareJID());
                lock.lock();
                try {
                    this.capabilitiesBeingUpdated.remove( packet.getFrom() );
                    oldVer = this.entityCapabilitiesUserMap.remove( packet.getFrom() );
                } finally {
                    lock.unlock();
                }

                if ( oldVer != null ) {
                    checkObsolete( oldVer );
                }
            }
            return;
        }

        // Examine the packet and check if it has caps info,
        // if not -- do nothing by returning.
        Element capsElement = packet.getChildElement(ELEMENT, NAMESPACE);
        if (capsElement == null) {
            return;
        }

        // Examine the packet and check if it's in legacy format (pre version 1.4
        // of XEP-0115). If so, do nothing by returning.
        // TODO: if this packet is in legacy format, we SHOULD check the 'node',
        // 'ver', and 'ext' combinations as specified in the archived version
        // 1.3 of the specification, and cache the results. See JM-1447
        final String hashAttribute = capsElement.attributeValue("hash");
        if (hashAttribute == null || hashAttribute.trim().isEmpty()) {
            return;
        }
        
        // Examine the packet and check if it has and a 'ver' hash
        // if not -- do nothing by returning.
        final String newVerAttribute = capsElement.attributeValue("ver");
        if (newVerAttribute == null || newVerAttribute.trim().isEmpty()) {
            return;
        }

        // Check to see if the 'ver' hash is already in our cache.
        EntityCapabilities caps;
        if ((caps = entityCapabilitiesMap.get(newVerAttribute)) != null) {
            // The 'ver' hash is in the cache already, so let's update the
            // entityCapabilitiesUserMap for the user that sent the caps
            // packet.
            Log.trace( "Registering 'ver' (for recognized caps) for {}", packet.getFrom() );
            registerCapabilities( packet.getFrom(), caps );
        }
        else {
            // OF-2696: Do not attempt to resolve the 'ver' hash if the entity that sent it is a MUC occupant. Openfire
            // will generate a lot of requests when users are joining busy MUCs, but as Openfire is not an occupant of
            // those MUCS, its requests are likely to fail anyway.
            if (packet.getChildElement("x", "http://jabber.org/protocol/muc") != null
                || packet.getChildElement("x", "http://jabber.org/protocol/muc#user") != null
                || packet.getChildElement("x", "http://jabber.org/protocol/muc#admin") != null
                || packet.getChildElement("x", "http://jabber.org/protocol/muc#owner") != null) {
                return;
            }

            final Lock lock = this.entityCapabilitiesUserMap.getLock(packet.getFrom().asBareJID());
            lock.lock();
            try {
                // If this entity previously had another registration, that now no longer is valid.
                final String ver = entityCapabilitiesUserMap.remove(packet.getFrom());
                if ( ver != null ) {
                    capabilitiesBeingUpdated.put( packet.getFrom(), ver );
                }
            } finally {
                lock.unlock();
            }

            // The 'ver' hash is not in the cache so send out a disco#info query
            // so that we may begin recognizing this 'ver' hash.
            IQ iq = new IQ(IQ.Type.get);
            iq.setTo(packet.getFrom());

            String serverName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
            iq.setFrom(serverName);

            iq.setChildElement("query", "http://jabber.org/protocol/disco#info");

            String packetId = iq.getID();
            
            caps = new EntityCapabilities();
            caps.setHashAttribute(hashAttribute);
            caps.setVerAttribute(newVerAttribute);
            Log.trace( "Querying 'ver' for unrecognized caps. Querying: {}", packet.getFrom() );
            verAttributes.put(packetId, caps);

            final IQRouter iqRouter = XMPPServer.getInstance().getIQRouter();
            iqRouter.addIQResultListener(packetId, this);
            iqRouter.route(iq);
        }
    }
Listeners that are invoked when new or changed capabilities for a specific entity are detected.
xmppserver/src/main/java/org/jivesoftware/openfire/entitycaps/EntityCapabilitiesManager.java