📄 multiuserchatserverimpl.java
字号:
/**
* $RCSfile: MultiUserChatServerImpl.java,v $
* $Revision: 3036 $
* $Date: 2005-11-07 15:15:00 -0300 (Mon, 07 Nov 2005) $
*
* Copyright (C) 2008 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution, or a commercial license
* agreement with Jive.
*/
package org.jivesoftware.openfire.muc.spi;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.jivesoftware.openfire.*;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.cluster.ClusterEventListener;
import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.disco.*;
import org.jivesoftware.openfire.forms.DataForm;
import org.jivesoftware.openfire.forms.FormField;
import org.jivesoftware.openfire.forms.spi.XDataFormImpl;
import org.jivesoftware.openfire.forms.spi.XFormFieldImpl;
import org.jivesoftware.openfire.muc.*;
import org.jivesoftware.openfire.muc.cluster.*;
import org.jivesoftware.openfire.resultsetmanager.ResultSet;
import org.jivesoftware.openfire.stats.Statistic;
import org.jivesoftware.openfire.stats.StatisticsManager;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.CacheFactory;
import org.xmpp.component.ComponentManager;
import org.xmpp.packet.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* Implements the chat server as a cached memory resident chat server. The server is also
* responsible for responding Multi-User Chat disco requests as well as removing inactive users from
* the rooms after a period of time and to maintain a log of the conversation in the rooms that
* require to log their conversations. The conversations log is saved to the database using a
* separate process<p>
*
* Temporary rooms are held in memory as long as they have occupants. They will be destroyed after
* the last occupant left the room. On the other hand, persistent rooms are always present in memory
* even after the last occupant left the room. In order to keep memory clean of persistent rooms that
* have been forgotten or abandoned this class includes a clean up process. The clean up process
* will remove from memory rooms that haven't had occupants for a while. Moreover, forgotten or
* abandoned rooms won't be loaded into memory when the Multi-User Chat service starts up.
*
* @author Gaston Dombiak
*/
public class MultiUserChatServerImpl extends BasicModule implements MultiUserChatServer,
ServerItemsProvider, DiscoInfoProvider, DiscoItemsProvider, RoutableChannelHandler, ClusterEventListener {
private static final FastDateFormat dateFormatter = FastDateFormat
.getInstance(JiveConstants.XMPP_DELAY_DATETIME_FORMAT, TimeZone.getTimeZone("UTC"));
/**
* Statistics keys
*/
private static final String roomsStatKey = "muc_rooms";
private static final String occupantsStatKey = "muc_occupants";
private static final String usersStatKey = "muc_users";
private static final String incomingStatKey = "muc_incoming";
private static final String outgoingStatKey = "muc_outgoing";
private static final String trafficStatGroup = "muc_traffic";
/**
* The time to elapse between clearing of idle chat users.
*/
private int user_timeout = 300000;
/**
* The number of milliseconds a user must be idle before he/she gets kicked from all the rooms.
*/
private int user_idle = -1;
/**
* Task that kicks idle users from the rooms.
*/
private UserTimeoutTask userTimeoutTask;
/**
* The time to elapse between logging the room conversations.
*/
private int log_timeout = 300000;
/**
* The number of messages to log on each run of the logging process.
*/
private int log_batch_size = 50;
/**
* Task that flushes room conversation logs to the database.
*/
private LogConversationTask logConversationTask;
/**
* the chat service's hostname
*/
private String chatServiceName = null;
/**
* chatrooms managed by this manager, table: key room name (String); value ChatRoom
*/
private Map<String, LocalMUCRoom> rooms = new ConcurrentHashMap<String, LocalMUCRoom>();
/**
* Chat users managed by this manager. This includes only users connected to this JVM.
* That means that when running inside of a cluster each node will have its own manager
* that in turn will keep its own list of locally connected.
*
* table: key user jid (XMPPAddress); value ChatUser
*/
private Map<JID, LocalMUCUser> users = new ConcurrentHashMap<JID, LocalMUCUser>();
private HistoryStrategy historyStrategy;
private RoutingTable routingTable = null;
/**
* The packet router for the server.
*/
private PacketRouter router = null;
/**
* The handler of packets with namespace jabber:iq:register for the server.
*/
private IQMUCRegisterHandler registerHandler = null;
/**
* The handler of search requests ('jabber:iq:search' namespace).
*/
private IQMUCSearchHandler searchHandler = null;
/**
* The total time all agents took to chat *
*/
public long totalChatTime;
/**
* Timer to monitor chatroom participants. If they've been idle for too long, probe for
* presence.
*/
private Timer timer = new Timer("MUC cleanup");
/**
* Flag that indicates if the service should provide information about locked rooms when
* handling service discovery requests.
* Note: Setting this flag in false is not compliant with the spec. A user may try to join a
* locked room thinking that the room doesn't exist because the user didn't discover it before.
*/
private boolean allowToDiscoverLockedRooms = true;
/**
* Returns the permission policy for creating rooms. A true value means that not anyone can
* create a room, only the JIDs listed in <code>allowedToCreate</code> are allowed to create
* rooms.
*/
private boolean roomCreationRestricted = false;
/**
* Bare jids of users that are allowed to create MUC rooms. An empty list means that anyone can
* create a room.
*/
private List<String> allowedToCreate = new CopyOnWriteArrayList<String>();
/**
* Bare jids of users that are system administrators of the MUC service. A sysadmin has the same
* permissions as a room owner.
*/
private List<String> sysadmins = new CopyOnWriteArrayList<String>();
/**
* Queue that holds the messages to log for the rooms that need to log their conversations.
*/
private Queue<ConversationLogEntry> logQueue = new LinkedBlockingQueue<ConversationLogEntry>();
/**
* Max number of hours that a persistent room may be empty before the service removes the
* room from memory. Unloaded rooms will exist in the database and may be loaded by a user
* request. Default time limit is: 30 days.
*/
private long emptyLimit = 30 * 24;
/**
* Task that removes rooms from memory that have been without activity for a period of time. A
* room is considered without activity when no occupants are present in the room for a while.
*/
private CleanupTask cleanupTask;
/**
* The time to elapse between each rooms cleanup. Default frequency is 60 minutes.
*/
private static final long CLEANUP_FREQUENCY = 60 * 60 * 1000;
/**
* Total number of received messages in all rooms since the last reset. The counter
* is reset each time the Statistic makes a sampling.
*/
private AtomicInteger inMessages = new AtomicInteger(0);
/**
* Total number of broadcasted messages in all rooms since the last reset. The counter
* is reset each time the Statistic makes a sampling.
*/
private AtomicLong outMessages = new AtomicLong(0);
/**
* Flag that indicates if MUC service is enabled.
*/
private boolean serviceEnabled = true;
Collection<MUCEventListener> listeners = new ConcurrentLinkedQueue<MUCEventListener>();
/**
* Create a new group chat server.
*/
public MultiUserChatServerImpl() {
super("Basic multi user chat server");
historyStrategy = new HistoryStrategy(null);
}
public String getDescription() {
return null;
}
public void process(Packet packet) throws UnauthorizedException, PacketException {
// TODO Remove this method when moving MUC as a component and removing module code
processPacket(packet);
}
public void processPacket(Packet packet) {
if (!isServiceEnabled()) {
return;
}
// The MUC service will receive all the packets whose domain matches the domain of the MUC
// service. This means that, for instance, a disco request should be responded by the
// service itself instead of relying on the server to handle the request.
try {
// Check if the packet is a disco request or a packet with namespace iq:register
if (packet instanceof IQ) {
if (process((IQ)packet)) {
return;
}
}
// The packet is a normal packet that should possibly be sent to the room
JID receipient = packet.getTo();
String roomName = receipient != null ? receipient.getNode() : null;
getChatUser(packet.getFrom(), roomName).process(packet);
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
}
/**
* Returns true if the IQ packet was processed. This method should only process disco packets
* as well as jabber:iq:register packets sent to the MUC service.
*
* @param iq the IQ packet to process.
* @return true if the IQ packet was processed.
*/
private boolean process(IQ iq) {
Element childElement = iq.getChildElement();
String namespace = null;
// Ignore IQs of type ERROR
if (IQ.Type.error == iq.getType()) {
return false;
}
if (iq.getTo().getResource() != null) {
// Ignore IQ packets sent to room occupants
return false;
}
if (childElement != null) {
namespace = childElement.getNamespaceURI();
}
if ("jabber:iq:register".equals(namespace)) {
IQ reply = registerHandler.handleIQ(iq);
router.route(reply);
}
else if ("jabber:iq:search".equals(namespace)) {
IQ reply = searchHandler.handleIQ(iq);
router.route(reply);
}
else if ("http://jabber.org/protocol/disco#info".equals(namespace)) {
// TODO MUC should have an IQDiscoInfoHandler of its own when MUC becomes
// a component
IQ reply = XMPPServer.getInstance().getIQDiscoInfoHandler().handleIQ(iq);
router.route(reply);
}
else if ("http://jabber.org/protocol/disco#items".equals(namespace)) {
// TODO MUC should have an IQDiscoItemsHandler of its own when MUC becomes
// a component
IQ reply = XMPPServer.getInstance().getIQDiscoItemsHandler().handleIQ(iq);
router.route(reply);
}
else {
return false;
}
return true;
}
public void initialize(JID jid, ComponentManager componentManager) {
}
public void shutdown() {
}
public String getServiceDomain() {
return chatServiceName + "." + XMPPServer.getInstance().getServerInfo().getXMPPDomain();
}
public JID getAddress() {
return new JID(null, getServiceDomain(), null, true);
}
/**
* Probes the presence of any user who's last packet was sent more than 5 minute ago.
*/
private class UserTimeoutTask extends TimerTask {
/**
* Remove any user that has been idle for longer than the user timeout time.
*/
public void run() {
checkForTimedOutUsers();
}
}
private void checkForTimedOutUsers() {
final long deadline = System.currentTimeMillis() - user_idle;
for (LocalMUCUser user : users.values()) {
try {
// If user is not present in any room then remove the user from
// the list of users
if (!user.isJoined()) {
removeUser(user.getAddress());
continue;
}
// Do nothing if this feature is disabled (i.e USER_IDLE equals -1)
if (user_idle == -1) {
continue;
}
if (user.getLastPacketTime() < deadline) {
// Kick the user from all the rooms that he/she had previuosly joined
MUCRoom room;
Presence kickedPresence;
for (LocalMUCRole role : user.getRoles()) {
room = role.getChatRoom();
try {
kickedPresence =
room.kickOccupant(user.getAddress(), null, null);
// Send the updated presence to the room occupants
room.send(kickedPresence);
}
catch (NotAllowedException e) {
// Do nothing since we cannot kick owners or admins
}
}
}
}
catch (Throwable e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
}
}
/**
* Logs the conversation of the rooms that have this feature enabled.
*/
private class LogConversationTask extends TimerTask {
public void run() {
try {
logConversation();
}
catch (Throwable e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
}
}
private void logConversation() {
ConversationLogEntry entry;
boolean success;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -