OfflineMessageStrategy.java 10.3 KB
Newer Older
1 2 3 4 5
/**
 * $RCSfile: OfflineMessageStrategy.java,v $
 * $Revision: 3114 $
 * $Date: 2005-11-23 18:12:54 -0300 (Wed, 23 Nov 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;
22

23 24
import java.util.Collections;
import java.util.Iterator;
25 26 27
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

28
import org.jivesoftware.openfire.container.BasicModule;
29
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
30 31 32
import org.jivesoftware.openfire.privacy.PrivacyList;
import org.jivesoftware.openfire.privacy.PrivacyListManager;
import org.jivesoftware.openfire.user.UserManager;
Gaston Dombiak's avatar
Gaston Dombiak committed
33
import org.jivesoftware.util.JiveGlobals;
34 35
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
36 37 38
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.PacketError;
39
import org.xmpp.packet.PacketExtension;
40 41 42 43 44 45

/**
 * Controls what is done with offline messages.
 *
 * @author Iain Shigeoka
 */
46
public class OfflineMessageStrategy extends BasicModule implements ServerFeaturesProvider {
47

48 49
	private static final Logger Log = LoggerFactory.getLogger(OfflineMessageStrategy.class);

50 51 52
    private static int quota = 100*1024; // Default to 100 K.
    private static Type type = Type.store_and_bounce;

53 54
    private static List<OfflineMessageListener> listeners = new CopyOnWriteArrayList<OfflineMessageListener>();

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
    private OfflineMessageStore messageStore;
    private JID serverAddress;
    private PacketRouter router;

    public OfflineMessageStrategy() {
        super("Offline Message Strategy");
    }

    public int getQuota() {
        return quota;
    }

    public void setQuota(int quota) {
        OfflineMessageStrategy.quota = quota;
        JiveGlobals.setProperty("xmpp.offline.quota", Integer.toString(quota));
    }

    public OfflineMessageStrategy.Type getType() {
        return type;
    }

    public void setType(OfflineMessageStrategy.Type type) {
        if (type == null) {
            throw new IllegalArgumentException();
        }
        OfflineMessageStrategy.type = type;
        JiveGlobals.setProperty("xmpp.offline.type", type.toString());
    }

    public void storeOffline(Message message) {
        if (message != null) {
Gaston Dombiak's avatar
Gaston Dombiak committed
86
            // Do nothing if the message was sent to the server itself, an anonymous user or a non-existent user
87
        	// Also ignore message carbons
88 89
            JID recipientJID = message.getTo();
            if (recipientJID == null || serverAddress.equals(recipientJID) ||
90
                    recipientJID.getNode() == null ||
91
                    message.getExtension("received", "urn:xmpp:carbons:2") != null ||
92
                    !UserManager.getInstance().isRegisteredUser(recipientJID.getNode())) {
93 94
                return;
            }
95

96 97 98 99
            // Do not store messages if communication is blocked
            PrivacyList list =
                    PrivacyListManager.getInstance().getDefaultPrivacyList(recipientJID.getNode());
            if (list != null && list.shouldBlockPacket(message)) {
csh's avatar
csh committed
100 101 102 103
                Message result = message.createCopy();
                result.setTo(message.getFrom());
                result.setError(PacketError.Condition.service_unavailable);
                XMPPServer.getInstance().getRoutingTable().routePacket(message.getFrom(), result, true);
104 105
                return;
            }
106

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
            // 8.5.2.  localpart@domainpart
            // 8.5.2.2.  No Available or Connected Resources
            if (recipientJID.getResource() == null) {
                if (message.getType() == Message.Type.headline || message.getType() == Message.Type.error) {
                    // For a message stanza of type "headline" or "error", the server MUST silently ignore the message.
                    return;
                }
                // // For a message stanza of type "groupchat", the server MUST return an error to the sender, which SHOULD be <service-unavailable/>.
                else if (message.getType() == Message.Type.groupchat) {
                    bounce(message);
                    return;
                }
            } else {
                // 8.5.3.  localpart@domainpart/resourcepart
                // 8.5.3.2.1.  Message

                // For a message stanza of type "normal", "groupchat", or "headline", the server MUST either (a) silently ignore the stanza
                // or (b) return an error stanza to the sender, which SHOULD be <service-unavailable/>.
                if (message.getType() == Message.Type.normal || message.getType() == Message.Type.groupchat || message.getType() == Message.Type.headline) {
126
                    // Depending on the OfflineMessageStragey, we may silently ignore or bounce
127 128 129
                    if (type == Type.bounce) {
                        bounce(message);
                    }
130 131
                    // Either bounce or silently ignore, never store such messages
                    return;
132 133 134 135 136 137 138
                }
                // For a message stanza of type "error", the server MUST silently ignore the stanza.
                else if (message.getType() == Message.Type.error) {
                    return;
                }
            }

139 140
            switch (type) {
            case bounce:
141
                bounce(message);
142 143
                break;
            case store:
144
                store(message);
145 146
                break;
            case store_and_bounce:
147 148 149 150 151 152
                if (underQuota(message)) {
                    store(message);
                }
                else {
                    bounce(message);
                }
153 154
                break;
            case store_and_drop:
155 156 157
                if (underQuota(message)) {
                    store(message);
                }
158 159 160 161
                break;
            case drop:
                // Drop essentially means silently ignore/do nothing
                break;
162 163 164 165
            }
        }
    }

166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
    /**
     * Registers a listener to receive events.
     *
     * @param listener the listener.
     */
    public static void addListener(OfflineMessageListener listener) {
        if (listener == null) {
            throw new NullPointerException();
        }
        listeners.add(listener);
    }

    /**
     * Unregisters a listener to receive events.
     *
     * @param listener the listener.
     */
    public static void removeListener(OfflineMessageListener listener) {
        listeners.remove(listener);
    }

187 188 189 190 191 192
    private boolean underQuota(Message message) {
        return quota > messageStore.getSize(message.getTo().getNode()) + message.toXML().length();
    }

    private void store(Message message) {
        messageStore.addMessage(message);
193 194 195 196 197 198
        // Inform listeners that an offline message was stored
        if (!listeners.isEmpty()) {
            for (OfflineMessageListener listener : listeners) {
                listener.messageStored(message);
            }
        }
199 200 201 202 203 204 205 206 207 208
    }

    private void bounce(Message message) {
        // Do nothing if the sender was the server itself
        if (message.getFrom() == null) {
            return;
        }
        try {
            // Generate a rejection response to the sender
            Message errorResponse = message.createCopy();
209 210
            // return an error stanza to the sender, which SHOULD be <service-unavailable/>
            errorResponse.setError(PacketError.Condition.service_unavailable);
211 212 213 214
            errorResponse.setFrom(message.getTo());
            errorResponse.setTo(message.getFrom());
            // Send the response
            router.route(errorResponse);
215 216 217 218 219 220
            // Inform listeners that an offline message was bounced
            if (!listeners.isEmpty()) {
                for (OfflineMessageListener listener : listeners) {
                    listener.messageBounced(message);
                }
            }
221 222
        }
        catch (Exception e) {
223
            Log.error(e.getMessage(), e);
224 225 226
        }
    }

227 228
    @Override
	public void initialize(XMPPServer server) {
229 230 231
        super.initialize(server);
        messageStore = server.getOfflineMessageStore();
        router = server.getPacketRouter();
232
        serverAddress = new JID(server.getServerInfo().getXMPPDomain());
233

234 235 236
        JiveGlobals.migrateProperty("xmpp.offline.quota");
        JiveGlobals.migrateProperty("xmpp.offline.type");

237 238 239 240 241 242 243 244 245 246
        String quota = JiveGlobals.getProperty("xmpp.offline.quota");
        if (quota != null && quota.length() > 0) {
            OfflineMessageStrategy.quota = Integer.parseInt(quota);
        }
        String type = JiveGlobals.getProperty("xmpp.offline.type");
        if (type != null && type.length() > 0) {
            OfflineMessageStrategy.type = Type.valueOf(type);
        }
    }

247 248 249 250 251 252 253 254 255 256 257 258
    @Override
    public Iterator<String> getFeatures() {
        switch (type) {
            case store:
            case store_and_bounce:
            case store_and_drop:
                // http://xmpp.org/extensions/xep-0160.html#disco
                return Collections.singleton("msgoffline").iterator();
        }
        return Collections.<String>emptyList().iterator();
    }

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
    /**
     * Strategy types.
     */
    public enum Type {

        /**
         * All messages are bounced to the sender.
         */
        bounce,

        /**
         * All messages are silently dropped.
         */
        drop,

        /**
         * All messages are stored.
         */
        store,

        /**
         * Messages are stored up to the storage limit, and then bounced.
         */
        store_and_bounce,

        /**
         * Messages are stored up to the storage limit, and then silently dropped.
         */
Gaston Dombiak's avatar
Gaston Dombiak committed
287
        store_and_drop
288 289
    }
}