class IQOfflineMessagesHandler extends IQHandler implements ServerFeaturesProvider,
        DiscoInfoProvider, DiscoItemsProvider {

    private static final Logger Log = LoggerFactory.getLogger(IQOfflineMessagesHandler.class);

    private static final String NAMESPACE = "http://jabber.org/protocol/offline";

    final private XMPPDateTimeFormat xmppDateTime = new XMPPDateTimeFormat();
    private IQHandlerInfo info;
    private IQDiscoInfoHandler infoHandler;
    private IQDiscoItemsHandler itemsHandler;

    private RoutingTable routingTable;
    private UserManager userManager;
    private OfflineMessageStore messageStore;

    public IQOfflineMessagesHandler() {
        super("Flexible Offline Message Retrieval Handler");
        info = new IQHandlerInfo("offline", NAMESPACE);
    }

    @Override
    public IQ handleIQ(IQ packet) throws UnauthorizedException {
        IQ reply = IQ.createResultIQ(packet);
        Element offlineRequest = packet.getChildElement();

        JID from = packet.getFrom();
        if (offlineRequest.element("purge") != null) {
            // User requested to delete all offline messages
            messageStore.deleteMessages(from.getNode());
        }
        else if (offlineRequest.element("fetch") != null) {
            // Mark that offline messages shouldn't be sent when the user becomes available
            stopOfflineFlooding(from);
            // User requested to receive all offline messages
            for (OfflineMessage offlineMessage : messageStore.getMessages(from.getNode(), false)) {
                sendOfflineMessage(from, offlineMessage);
            }
        }
        else {
            for (Iterator it = offlineRequest.elementIterator("item"); it.hasNext();) {
                Element item = (Element) it.next();
                Date creationDate = null;
                try {
                    creationDate = xmppDateTime.parseString(item.attributeValue("node"));
                } catch (ParseException e) {
                    Log.error("Error parsing date", e);
                }
                if ("view".equals(item.attributeValue("action"))) {
                    // User requested to receive specific message
                    OfflineMessage offlineMsg = messageStore.getMessage(from.getNode(), creationDate);
                    if (offlineMsg != null) {
                        sendOfflineMessage(from, offlineMsg);
                    } else {
                        // If the requester is authorized but the node does not exist, the server MUST return a <item-not-found/> error.
                        reply.setError(PacketError.Condition.item_not_found);
                    }
                }
                else if ("remove".equals(item.attributeValue("action"))) {
                    // User requested to delete specific message
                    if (messageStore.getMessage(from.getNode(), creationDate) != null) {
                        messageStore.deleteMessage(from.getNode(), creationDate);
                    } else {
                        // If the requester is authorized but the node does not exist, the server MUST return a <item-not-found/> error.
                        reply.setError(PacketError.Condition.item_not_found);
                    }
                }
            }
        }
        return reply;
    }

    private void sendOfflineMessage(JID receipient, OfflineMessage offlineMessage) {
        Element offlineInfo = offlineMessage.addChildElement("offline", NAMESPACE);
        offlineInfo.addElement("item").addAttribute("node",
                XMPPDateTimeFormat.format(offlineMessage.getCreationDate()));
        routingTable.routePacket(receipient, offlineMessage);
    }

    @Override
    public IQHandlerInfo getInfo() {
        return info;
    }

    @Override
    public Iterator<String> getFeatures() {
        return Collections.singleton(NAMESPACE).iterator();
    }

    @Override
    public Iterator<Element> getIdentities(String name, String node, JID senderJID) {
        Element identity = DocumentHelper.createElement("identity");
        identity.addAttribute("category", "automation");
        identity.addAttribute("type", "message-list");
        return Collections.singleton(identity).iterator();
    }

    @Override
    public Iterator<String> getFeatures(String name, String node, JID senderJID) {
        return Collections.singleton(NAMESPACE).iterator();
    }

    @Override
    public Set<DataForm> getExtendedInfos(String name, String node, JID senderJID) {<FILL_FUNCTION_BODY>}

    @Override
    public boolean hasInfo(String name, String node, JID senderJID) {
        return NAMESPACE.equals(node) && userManager.isRegisteredUser(senderJID, false);
    }

    @Override
    public Iterator<DiscoItem> getItems(String name, String node, JID senderJID) {
        // Mark that offline messages shouldn't be sent when the user becomes available
        stopOfflineFlooding(senderJID);
        List<DiscoItem> answer = new ArrayList<>();
        for (OfflineMessage offlineMessage : messageStore.getMessages(senderJID.getNode(), false)) {
            answer.add(new DiscoItem(senderJID.asBareJID(), offlineMessage.getFrom().toString(),
                    XMPPDateTimeFormat.format(offlineMessage.getCreationDate()), null));
        }

        return answer.iterator();
    }

    @Override
    public void initialize(XMPPServer server) {
        super.initialize(server);
        infoHandler = server.getIQDiscoInfoHandler();
        itemsHandler = server.getIQDiscoItemsHandler();
        messageStore = server.getOfflineMessageStore();
        userManager = server.getUserManager();
        routingTable = server.getRoutingTable();
    }

    @Override
    public void start() throws IllegalStateException {
        super.start();
        infoHandler.setServerNodeInfoProvider(NAMESPACE, this);
        itemsHandler.setServerNodeInfoProvider(NAMESPACE, this);
    }

    @Override
    public void stop() {
        super.stop();
        infoHandler.removeServerNodeInfoProvider(NAMESPACE);
        itemsHandler.removeServerNodeInfoProvider(NAMESPACE);
    }

    private void stopOfflineFlooding(JID senderJID) {
        LocalClientSession session = (LocalClientSession) sessionManager.getSession(senderJID);
        if (session != null) {
            session.setOfflineFloodStopped(true);
        }
    }
}

        // Mark that offline messages shouldn't be sent when the user becomes available
        stopOfflineFlooding(senderJID);

        final DataForm dataForm = new DataForm(DataForm.Type.result);

        final FormField field1 = dataForm.addField();
        field1.setVariable("FORM_TYPE");
        field1.setType(FormField.Type.hidden);
        field1.addValue(NAMESPACE);

        final FormField field2 = dataForm.addField();
        field2.setVariable("number_of_messages");
        field2.addValue(String.valueOf(messageStore.getCount(senderJID.getNode())));
        
        final Set<DataForm> dataForms = new HashSet<>();
        dataForms.add(dataForm);
        return dataForms;
    
<methods>public void <init>(java.lang.String) ,public abstract org.jivesoftware.openfire.IQHandlerInfo getInfo() ,public abstract IQ handleIQ(IQ) throws org.jivesoftware.openfire.auth.UnauthorizedException,public void initialize(org.jivesoftware.openfire.XMPPServer) ,public boolean performNoSuchUserCheck() ,public void process(Packet) throws org.jivesoftware.openfire.PacketException<variables>private static final Logger Log,protected org.jivesoftware.openfire.PacketDeliverer deliverer,protected org.jivesoftware.openfire.SessionManager sessionManager