public IQ handleIQ( IQ iq )
    {
        Log.trace( "Received request: {}", iq );

        final IQ reply = IQ.createResultIQ( iq );
        if ( !PROPERTY_ENABLED.getValue() )
        {
            Log.debug( "Unable to process request: service has been disabled by configuration." );
            reply.setError( Condition.service_unavailable );
            return reply;
        }

        final Element formElement = iq.getChildElement().element( QName.get( "x", "jabber:x:data" ) );
        if ( formElement == null )
        {
            Log.debug( "Responding with a fresh search form." );
            reply.setChildElement( getDataElement() );
            return reply;
        }

        // Parse params from request.
        final SearchParameters params;
        try
        {
            params = parseSearchParameters( formElement );
        }
        catch ( IllegalArgumentException | ParseException e )
        {
            Log.debug( "Unable to parse search parameters from request.", e );
            if (e.getMessage().startsWith(ERRORMESSAGE_UNSUPPORTED_KEY_VALUE)) {
                reply.setError(new PacketError(Condition.feature_not_implemented, PacketError.Type.modify, "The sort key that was provided is not recognized."));
                reply.getError().setApplicationCondition("invalid-sort-key", "urn:xmpp:channel-search:0:error");
            } else {
                reply.setError(new PacketError(Condition.bad_request, PacketError.Type.modify, "Unable to successfully parse the search parameters from the provided form."));
            }
            return reply;
        }

        final Set<String> conflictingFields = getConflictingFields(params);
        if (!conflictingFields.isEmpty()) {
            Log.debug("Request from {} contained conflicting fields: {}", iq.getFrom(), String.join(", ", conflictingFields) );
            final PacketError error = new PacketError(Condition.bad_request, PacketError.Type.modify, "These fields cannot be used in the same request: '" + String.join("', '", conflictingFields) + "'.", "en");
            final Element fields = error.getElement().addElement("conflicting-fields", "urn:xmpp:channel-search:0:error");
            for (final String conflictingField : conflictingFields) {
                fields.addElement("var").setText(conflictingField);
            }
            reply.setError(error);
            return reply;
        }

        final Map<String, Set<String>> missingFields = getMissingFields(params);
        if (!missingFields.isEmpty()) {
            Log.debug("Request from {} contained missing fields: {}", iq.getFrom(), String.join(", ", missingFields.keySet()) );
            final PacketError error = new PacketError(Condition.bad_request, PacketError.Type.modify, "Some fields cannot be used without specifying other fields. Please add: '" + String.join("', '", missingFields.keySet())+"' or remove: '" + missingFields.values().stream().flatMap(Collection::stream).collect(Collectors.joining("', '")) + "'.", "en");
            final Element fields = error.getElement().addElement("conflicting-fields", "urn:xmpp:channel-search:0:error");
            for (final Collection<String> dependants : missingFields.values()) {
                for (String dependant : dependants) {
                    fields.addElement("var").setText(dependant);
                }
            }
            reply.setError(error);
            return reply;
        }

        if ((params.getAll() == null || !params.getAll()) && (params.getQ() == null || params.getQ().isEmpty())) {
            Log.debug("Request from {} didn't contain any search conditions.", iq.getFrom());
            final PacketError error = new PacketError(Condition.bad_request, PacketError.Type.cancel, "A search request needs to contain a search condition. Please add either 'q' or 'all'.", "en");
            error.setApplicationCondition("no-search-conditions", "urn:xmpp:channel-search:0:error");
            reply.setError(error);
            return reply;
        }

        // Search for chatrooms matching the request params.
        List<MUCRoomSearchInfo> mucs = searchForChatrooms( params );

        final Key key;
        if (params.getKey() != null ) {
            // Use the value provided by the client.
            key = params.getKey();
        } else {
            // Use the default key defined by the server.
            key = Key.valueOf(new DataForm(getDataElement().element(QName.get(DataForm.ELEMENT_NAME, DataForm.NAMESPACE))).getField(VAR_KEY).getFirstValue());
        }

        switch (key) {
            case nusers:
                mucs = sortByUserAmount(mucs);
                break;

            case address:
                mucs = sortByAddress(mucs);
                break;
        }

        final ResultSet<MUCRoomSearchInfo> searchResults = new ResultSetImpl<>( mucs );

        // See if the requesting entity would like to apply 'result set management'
        final Element set = iq.getChildElement().element( QName.get( "set", ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT ) );
        final List<MUCRoomSearchInfo> mucrsm;

        // apply RSM only if the element exists, and the (total) results set is not empty.
        final boolean applyRSM = set != null && !mucs.isEmpty();

        if ( applyRSM )
        {
            Log.trace( "Applying RSM" );
            if ( !ResultSet.isValidRSMRequest( set ) )
            {
                reply.setError( Condition.bad_request );
                return reply;
            }

            try
            {
                mucrsm = searchResults.applyRSMDirectives( set );
            }
            catch ( NullPointerException e )
            {
                final IQ itemNotFound = IQ.createResultIQ( iq );
                itemNotFound.setError( Condition.item_not_found );
                return itemNotFound;
            }
        }
        else
        {
            // if no rsm, all found rooms are part of the result.
            Log.trace( "Not applying RSM" );
            mucrsm = new ArrayList<>( searchResults );
        }

        final Element res = generateResultElement( mucrsm );

        res.add( searchResults.generateSetElementFromResults( mucrsm ) );

        reply.setChildElement( res );

        return reply;
    }
Constructs an answer on a IQ stanza that contains a search request. The
answer will be an IQ stanza of type 'result' or 'error'.

@param iq The IQ stanza that is the search request.
@return An answer to the provided request.
xmppserver/src/main/java/org/jivesoftware/openfire/muc/spi/IQExtendedChannelSearchHandler.java