org.jivesoftware.openfire.OfflineMessageStrategy Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2005-2008 Jive Software. All rights reserved.
*
* 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.
*/
package org.jivesoftware.openfire;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
import org.jivesoftware.openfire.privacy.PrivacyList;
import org.jivesoftware.openfire.privacy.PrivacyListManager;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.PacketError;
/**
* Controls what is done with offline messages.
*
* @author Iain Shigeoka
*/
public class OfflineMessageStrategy extends BasicModule implements ServerFeaturesProvider {
private static final Logger Log = LoggerFactory.getLogger(OfflineMessageStrategy.class);
private static int quota = 100*1024; // Default to 100 K.
private static Type type = Type.store_and_bounce;
private static List listeners = new CopyOnWriteArrayList<>();
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) {
// Do nothing if the message was sent to the server itself, an anonymous user or a non-existent user
// Also ignore message carbons
JID recipientJID = message.getTo();
if (recipientJID == null || serverAddress.equals(recipientJID) ||
recipientJID.getNode() == null ||
message.getExtension("received", "urn:xmpp:carbons:2") != null ||
!UserManager.getInstance().isRegisteredUser(recipientJID.getNode())) {
return;
}
// Do not store messages if communication is blocked
PrivacyList list =
PrivacyListManager.getInstance().getDefaultPrivacyList(recipientJID.getNode());
if (list != null && list.shouldBlockPacket(message)) {
Message result = message.createCopy();
result.setTo(message.getFrom());
result.setFrom(message.getTo());
result.setError(PacketError.Condition.service_unavailable);
XMPPServer.getInstance().getRoutingTable().routePacket(message.getFrom(), result, true);
return;
}
// 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 .
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 .
if (message.getType() == Message.Type.normal || message.getType() == Message.Type.groupchat || message.getType() == Message.Type.headline) {
// Depending on the OfflineMessageStragey, we may silently ignore or bounce
if (type == Type.bounce) {
bounce(message);
}
// Either bounce or silently ignore, never store such messages
return;
}
// For a message stanza of type "error", the server MUST silently ignore the stanza.
else if (message.getType() == Message.Type.error) {
return;
}
}
switch (type) {
case bounce:
bounce(message);
break;
case store:
store(message);
break;
case store_and_bounce:
if (underQuota(message)) {
store(message);
}
else {
Log.debug( "Unable to store, as user is over storage quota. Bouncing message instead: " + message.toXML() );
bounce(message);
}
break;
case store_and_drop:
if (underQuota(message)) {
store(message);
} else {
Log.debug( "Unable to store, as user is over storage quota. Silently dropping message: " + message.toXML() );
}
break;
case drop:
// Drop essentially means silently ignore/do nothing
break;
}
}
}
/**
* 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);
}
private boolean underQuota(Message message) {
return quota > messageStore.getSize(message.getTo().getNode()) + message.toXML().length();
}
private void store(Message message) {
messageStore.addMessage(message);
// Inform listeners that an offline message was stored
if (!listeners.isEmpty()) {
for (OfflineMessageListener listener : listeners) {
listener.messageStored(message);
}
}
}
private void bounce(Message message) {
// Do nothing if the sender was the server itself
if (message.getFrom() == null || message.getFrom().equals( serverAddress )) {
return;
}
try {
// Generate a rejection response to the sender
Message errorResponse = message.createCopy();
// return an error stanza to the sender, which SHOULD be
errorResponse.setError(PacketError.Condition.service_unavailable);
errorResponse.setFrom(message.getTo());
errorResponse.setTo(message.getFrom());
// Send the response
router.route(errorResponse);
// Inform listeners that an offline message was bounced
if (!listeners.isEmpty()) {
for (OfflineMessageListener listener : listeners) {
listener.messageBounced(message);
}
}
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
@Override
public void initialize(XMPPServer server) {
super.initialize(server);
messageStore = server.getOfflineMessageStore();
router = server.getPacketRouter();
serverAddress = new JID(server.getServerInfo().getXMPPDomain());
JiveGlobals.migrateProperty("xmpp.offline.quota");
JiveGlobals.migrateProperty("xmpp.offline.type");
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);
}
}
@Override
public Iterator 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.emptyList().iterator();
}
/**
* 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.
*/
store_and_drop
}
}