public Future<?> process(final PubSubService service, final IQ iq) {
        // Ignore IQs of type ERROR or RESULT
        if (IQ.Type.error == iq.getType() || IQ.Type.result == iq.getType()) {
            return new ImmediateFuture<>();
        }
        final Element childElement = iq.getChildElement();
        String namespace = null;

        if (childElement != null) {
            namespace = childElement.getNamespaceURI();
        }
        if ("http://jabber.org/protocol/pubsub".equals(namespace)) {
            Element action = childElement.element("publish");
            if (action != null) {
                // Entity publishes an item
                // Complete this asynchronously, as UserManager::isRegisteredUser(JID) blocks, waiting for a result which may come in on this thread
                final Element finalAction = action;
                return TaskEngine.getInstance().submit(() -> publishItemsToNode(service, iq, finalAction));
            }
            action = childElement.element("subscribe");
            if (action != null) {
                // Entity subscribes to a node
                return subscribeNode(service, iq, childElement, action);
            }
            action = childElement.element("options");
            if (action != null) {
                if (IQ.Type.get == iq.getType()) {
                    // Subscriber requests subscription options form
                    getSubscriptionConfiguration(service, iq, childElement, action);
                }
                else {
                    // Subscriber submits completed options form
                    configureSubscription(service, iq, action);
                }
                return new ImmediateFuture<>();
            }
            action = childElement.element("create");
            if (action != null) {
                // Entity is requesting to create a new node
                final Element finalAction = action;
                // Complete this asynchronously, as UserManager::isRegisteredUser(JID) blocks, waiting for a result which may come in on this thread
                return TaskEngine.getInstance().submit(() -> createNode(service, iq, childElement, finalAction, getPublishOptions( iq )));
            }
            action = childElement.element("unsubscribe");
            if (action != null) {
                // Entity unsubscribes from a node
                unsubscribeNode(service, iq, action);
                return new ImmediateFuture<>();
            }
            action = childElement.element("subscriptions");
            if (action != null) {
                // Entity requests all current subscriptions
                getSubscriptions(service, iq, childElement);
                return new ImmediateFuture<>();
            }
            action = childElement.element("affiliations");
            if (action != null) {
                // Entity requests all current affiliations
                getAffiliations(service, iq, childElement);
                return new ImmediateFuture<>();
            }
            action = childElement.element("items");
            if (action != null) {
                // Subscriber requests all active items
                getPublishedItems(service, iq, action);
                return new ImmediateFuture<>();
            }
            action = childElement.element("retract");
            if (action != null) {
                // Entity deletes an item
                deleteItems(service, iq, action);
                return new ImmediateFuture<>();
            }
            // Unknown action requested
            sendErrorPacket(iq, PacketError.Condition.bad_request, null);
            return new ImmediateFuture<>();
        }
        else if ("http://jabber.org/protocol/pubsub#owner".equals(namespace)) {
            Element action = childElement.element("configure");
            if (action != null) {
                String nodeID = action.attributeValue("node");
                if (nodeID == null) {
                    // if user is not sysadmin then return nodeid-required error
                    if (!service.isServiceAdmin(iq.getFrom()) ||
                            !service.isCollectionNodesSupported()) {
                        // Configure elements must have a node attribute so answer an error
                        Element pubsubError = DocumentHelper.createElement(QName.get(
                                "nodeid-required", "http://jabber.org/protocol/pubsub#errors"));
                        sendErrorPacket(iq, PacketError.Condition.bad_request, pubsubError);
                        return new ImmediateFuture<>();
                    }
                    else {
                        // Sysadmin is trying to configure root collection node
                        nodeID = service.getRootCollectionNode().getUniqueIdentifier().getNodeId();
                    }
                }
                if (IQ.Type.get == iq.getType()) {
                    // Owner requests configuration form of a node
                    getNodeConfiguration(service, iq, childElement, nodeID);
                }
                else {
                    // Owner submits or cancels node configuration form
                    configureNode(service, iq, action, nodeID);
                }
                return new ImmediateFuture<>();
            }
            action = childElement.element("default");
            if (action != null) {
                // Owner requests default configuration options for
                // leaf or collection nodes
                getDefaultNodeConfiguration(service, iq, childElement, action);
                return new ImmediateFuture<>();
            }
            action = childElement.element("delete");
            if (action != null) {
                // Owner deletes a node
                deleteNode(service, iq, action);
                return new ImmediateFuture<>();
            }
            action = childElement.element("subscriptions");
            if (action != null) {
                if (IQ.Type.get == iq.getType()) {
                    // Owner requests all affiliated entities
                    getNodeSubscriptions(service, iq, action);
                }
                else {
                    modifyNodeSubscriptions(service, iq, action);
                }
                return new ImmediateFuture<>();
            }
            action = childElement.element("affiliations");
            if (action != null) {
                if (IQ.Type.get == iq.getType()) {
                    // Owner requests all affiliated entities
                    getNodeAffiliations(service, iq, action);
                }
                else {
                    modifyNodeAffiliations(service, iq, action);
                }
                return new ImmediateFuture<>();
            }
            action = childElement.element("purge");
            if (action != null) {
                // Owner purges items from a node
                purgeNode(service, iq, action);
                return new ImmediateFuture<>();
            }
            // Unknown action requested so return error to sender
            sendErrorPacket(iq, PacketError.Condition.bad_request, null);
            return new ImmediateFuture<>();
        }
        else if ("http://jabber.org/protocol/commands".equals(namespace)) {
            // Process ad-hoc command
            IQ reply = service.getManager().process(iq);
            router.route(reply);
            return new ImmediateFuture<>();
        }
        return null;
    }
Handles IQ packets sent to the pubsub service. Requests of disco#info and disco#items
are not being handled by the engine. Instead the service itself should handle disco packets.

@param service the PubSub service this action is to be performed for.
@param iq      the IQ packet sent to the pubsub service.
@return <code>null</code> if the IQ packet was not handled by the engine, otherwise a {@link Future} that
indicates when processing is complete. Processing will be carried out asynchronously if there is the possibility
of sending a disco#info to a remote server, which could block for up to 60 seconds. If processing is carried out
synchronously, the returned future completes immediately. Note that the returned future will only return
<code>null</code> when it completes.
xmppserver/src/main/java/org/jivesoftware/openfire/pubsub/PubSubEngine.java