IQOfflineMessagesHandler.java 9.09 KB
Newer Older
1 2 3 4 5
/**
 * $RCSfile$
 * $Revision: 1761 $
 * $Date: 2005-08-09 19:34:09 -0300 (Tue, 09 Aug 2005) $
 *
6
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
7
 *
8 9 10 11 12 13 14 15 16 17 18
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
19 20
 */

21
package org.jivesoftware.openfire.handler;
22

23 24 25 26 27 28 29
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

30 31
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
32 33 34 35 36
import org.jivesoftware.openfire.IQHandlerInfo;
import org.jivesoftware.openfire.OfflineMessage;
import org.jivesoftware.openfire.OfflineMessageStore;
import org.jivesoftware.openfire.RoutingTable;
import org.jivesoftware.openfire.XMPPServer;
37
import org.jivesoftware.openfire.auth.UnauthorizedException;
38 39 40 41 42 43
import org.jivesoftware.openfire.disco.DiscoInfoProvider;
import org.jivesoftware.openfire.disco.DiscoItem;
import org.jivesoftware.openfire.disco.DiscoItemsProvider;
import org.jivesoftware.openfire.disco.IQDiscoInfoHandler;
import org.jivesoftware.openfire.disco.IQDiscoItemsHandler;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
44
import org.jivesoftware.openfire.session.LocalClientSession;
45
import org.jivesoftware.openfire.user.UserManager;
46
import org.jivesoftware.util.XMPPDateTimeFormat;
47 48
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
49 50
import org.xmpp.forms.DataForm;
import org.xmpp.forms.FormField;
51 52
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
csh's avatar
csh committed
53
import org.xmpp.packet.PacketError;
54 55 56 57 58 59 60 61 62 63 64

/**
 * Implements JEP-0013: Flexible Offline Message Retrieval. Allows users to request number of
 * messages, request message headers, retrieve specific messages, remove specific messages,
 * retrieve all messages and remove all messages.
 *
 * @author Gaston Dombiak
 */
public class IQOfflineMessagesHandler extends IQHandler implements ServerFeaturesProvider,
        DiscoInfoProvider, DiscoItemsProvider {

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

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

69
    final private XMPPDateTimeFormat xmppDateTime = new XMPPDateTimeFormat();
70 71 72 73
    private IQHandlerInfo info;
    private IQDiscoInfoHandler infoHandler;
    private IQDiscoItemsHandler itemsHandler;

74
    private RoutingTable routingTable;
75
    private UserManager userManager;
76 77 78 79 80 81 82
    private OfflineMessageStore messageStore;

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

83 84
    @Override
	public IQ handleIQ(IQ packet) throws UnauthorizedException {
85 86 87
        IQ reply = IQ.createResultIQ(packet);
        Element offlineRequest = packet.getChildElement();

88
        JID from = packet.getFrom();
89 90
        if (offlineRequest.element("purge") != null) {
            // User requested to delete all offline messages
91
            messageStore.deleteMessages(from.getNode());
92 93 94
        }
        else if (offlineRequest.element("fetch") != null) {
            // Mark that offline messages shouldn't be sent when the user becomes available
95
            stopOfflineFlooding(from);
96
            // User requested to receive all offline messages
97 98
            for (OfflineMessage offlineMessage : messageStore.getMessages(from.getNode(), false)) {
                sendOfflineMessage(from, offlineMessage);
99 100 101 102 103 104
            }
        }
        else {
            for (Iterator it = offlineRequest.elementIterator("item"); it.hasNext();) {
                Element item = (Element) it.next();
                Date creationDate = null;
105 106 107 108
                try {
                    creationDate = xmppDateTime.parseString(item.attributeValue("node"));
                } catch (ParseException e) {
                    Log.error("Error parsing date", e);
109 110 111
                }
                if ("view".equals(item.attributeValue("action"))) {
                    // User requested to receive specific message
112
                    OfflineMessage offlineMsg = messageStore.getMessage(from.getNode(), creationDate);
113
                    if (offlineMsg != null) {
114
                        sendOfflineMessage(from, offlineMsg);
115 116 117 118
                    }
                }
                else if ("remove".equals(item.attributeValue("action"))) {
                    // User requested to delete specific message
csh's avatar
csh committed
119 120 121 122 123 124
                    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);
                    }
125 126 127 128 129 130
                }
            }
        }
        return reply;
    }

131
    private void sendOfflineMessage(JID receipient, OfflineMessage offlineMessage) {
132
        Element offlineInfo = offlineMessage.addChildElement("offline", NAMESPACE);
133 134
        offlineInfo.addElement("item").addAttribute("node",
                XMPPDateTimeFormat.format(offlineMessage.getCreationDate()));
Gaston Dombiak's avatar
Gaston Dombiak committed
135
        routingTable.routePacket(receipient, offlineMessage, true);
136 137
    }

138 139
    @Override
	public IQHandlerInfo getInfo() {
140 141 142
        return info;
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
143
    public Iterator<String> getFeatures() {
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
        ArrayList<String> features = new ArrayList<String>();
        features.add(NAMESPACE);
        return features.iterator();
    }

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

    public Iterator<String> getFeatures(String name, String node, JID senderJID) {
        return Arrays.asList(NAMESPACE).iterator();
    }

162
    public DataForm getExtendedInfo(String name, String node, JID senderJID) {
163 164 165
        // Mark that offline messages shouldn't be sent when the user becomes available
        stopOfflineFlooding(senderJID);

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

168 169 170 171
        final FormField field1 = dataForm.addField();
        field1.setVariable("FORM_TYPE");
        field1.setType(FormField.Type.hidden);
        field1.addValue(NAMESPACE);
172

173 174 175
        final FormField field2 = dataForm.addField();
        field2.setVariable("number_of_messages");
        field2.addValue(String.valueOf(messageStore.getMessages(senderJID.getNode(), false).size()));
176 177 178 179 180

        return dataForm;
    }

    public boolean hasInfo(String name, String node, JID senderJID) {
181
        return NAMESPACE.equals(node) && userManager.isRegisteredUser(senderJID.getNode());
182 183
    }

184
    public Iterator<DiscoItem> getItems(String name, String node, JID senderJID) {
185 186
        // Mark that offline messages shouldn't be sent when the user becomes available
        stopOfflineFlooding(senderJID);
187
        List<DiscoItem> answer = new ArrayList<DiscoItem>();
188
        for (OfflineMessage offlineMessage : messageStore.getMessages(senderJID.getNode(), false)) {
189
            answer.add(new DiscoItem(senderJID.asBareJID(), offlineMessage.getFrom().toString(),
190
                    XMPPDateTimeFormat.format(offlineMessage.getCreationDate()), null));
191 192 193 194 195
        }

        return answer.iterator();
    }

196 197
    @Override
	public void initialize(XMPPServer server) {
198 199 200 201
        super.initialize(server);
        infoHandler = server.getIQDiscoInfoHandler();
        itemsHandler = server.getIQDiscoItemsHandler();
        messageStore = server.getOfflineMessageStore();
202
        userManager = server.getUserManager();
203
        routingTable = server.getRoutingTable();
204 205
    }

206 207
    @Override
	public void start() throws IllegalStateException {
208 209 210 211 212
        super.start();
        infoHandler.setServerNodeInfoProvider(NAMESPACE, this);
        itemsHandler.setServerNodeInfoProvider(NAMESPACE, this);
    }

213 214
    @Override
	public void stop() {
215 216 217 218 219 220
        super.stop();
        infoHandler.removeServerNodeInfoProvider(NAMESPACE);
        itemsHandler.removeServerNodeInfoProvider(NAMESPACE);
    }

    private void stopOfflineFlooding(JID senderJID) {
221
        LocalClientSession session = (LocalClientSession) sessionManager.getSession(senderJID);
222 223 224 225 226
        if (session != null) {
            session.setOfflineFloodStopped(true);
        }
    }
}