0001: /**
0002: * $RCSfile: MultiUserChatServerImpl.java,v $
0003: * $Revision: 3036 $
0004: * $Date: 2005-11-07 15:15:00 -0300 (Mon, 07 Nov 2005) $
0005: *
0006: * Copyright (C) 2004 Jive Software. All rights reserved.
0007: *
0008: * This software is published under the terms of the GNU Public License (GPL),
0009: * a copy of which is included in this distribution.
0010: */package org.jivesoftware.openfire.muc.spi;
0011:
0012: import org.dom4j.DocumentHelper;
0013: import org.dom4j.Element;
0014: import org.jivesoftware.openfire.*;
0015: import org.jivesoftware.openfire.auth.UnauthorizedException;
0016: import org.jivesoftware.openfire.cluster.ClusterEventListener;
0017: import org.jivesoftware.openfire.cluster.ClusterManager;
0018: import org.jivesoftware.openfire.container.BasicModule;
0019: import org.jivesoftware.openfire.disco.*;
0020: import org.jivesoftware.openfire.forms.DataForm;
0021: import org.jivesoftware.openfire.forms.FormField;
0022: import org.jivesoftware.openfire.forms.spi.XDataFormImpl;
0023: import org.jivesoftware.openfire.forms.spi.XFormFieldImpl;
0024: import org.jivesoftware.openfire.muc.*;
0025: import org.jivesoftware.openfire.muc.cluster.*;
0026: import org.jivesoftware.openfire.resultsetmanager.ResultSet;
0027: import org.jivesoftware.openfire.stats.Statistic;
0028: import org.jivesoftware.openfire.stats.StatisticsManager;
0029: import org.jivesoftware.util.*;
0030: import org.jivesoftware.util.cache.CacheFactory;
0031: import org.xmpp.component.ComponentManager;
0032: import org.xmpp.packet.*;
0033:
0034: import java.util.*;
0035: import java.util.concurrent.ConcurrentHashMap;
0036: import java.util.concurrent.ConcurrentLinkedQueue;
0037: import java.util.concurrent.CopyOnWriteArrayList;
0038: import java.util.concurrent.LinkedBlockingQueue;
0039: import java.util.concurrent.atomic.AtomicInteger;
0040: import java.util.concurrent.atomic.AtomicLong;
0041:
0042: /**
0043: * Implements the chat server as a cached memory resident chat server. The server is also
0044: * responsible for responding Multi-User Chat disco requests as well as removing inactive users from
0045: * the rooms after a period of time and to maintain a log of the conversation in the rooms that
0046: * require to log their conversations. The conversations log is saved to the database using a
0047: * separate process<p>
0048: *
0049: * Temporary rooms are held in memory as long as they have occupants. They will be destroyed after
0050: * the last occupant left the room. On the other hand, persistent rooms are always present in memory
0051: * even after the last occupant left the room. In order to keep memory clean of persistent rooms that
0052: * have been forgotten or abandoned this class includes a clean up process. The clean up process
0053: * will remove from memory rooms that haven't had occupants for a while. Moreover, forgotten or
0054: * abandoned rooms won't be loaded into memory when the Multi-User Chat service starts up.
0055: *
0056: * @author Gaston Dombiak
0057: */
0058: public class MultiUserChatServerImpl extends BasicModule implements
0059: MultiUserChatServer, ServerItemsProvider, DiscoInfoProvider,
0060: DiscoItemsProvider, RoutableChannelHandler,
0061: ClusterEventListener {
0062:
0063: private static final FastDateFormat dateFormatter = FastDateFormat
0064: .getInstance(JiveConstants.XMPP_DELAY_DATETIME_FORMAT,
0065: TimeZone.getTimeZone("UTC"));
0066:
0067: /**
0068: * Statistics keys
0069: */
0070: private static final String roomsStatKey = "muc_rooms";
0071: private static final String occupantsStatKey = "muc_occupants";
0072: private static final String usersStatKey = "muc_users";
0073: private static final String incomingStatKey = "muc_incoming";
0074: private static final String outgoingStatKey = "muc_outgoing";
0075: private static final String trafficStatGroup = "muc_traffic";
0076:
0077: /**
0078: * The time to elapse between clearing of idle chat users.
0079: */
0080: private int user_timeout = 300000;
0081: /**
0082: * The number of milliseconds a user must be idle before he/she gets kicked from all the rooms.
0083: */
0084: private int user_idle = -1;
0085: /**
0086: * Task that kicks idle users from the rooms.
0087: */
0088: private UserTimeoutTask userTimeoutTask;
0089: /**
0090: * The time to elapse between logging the room conversations.
0091: */
0092: private int log_timeout = 300000;
0093: /**
0094: * The number of messages to log on each run of the logging process.
0095: */
0096: private int log_batch_size = 50;
0097: /**
0098: * Task that flushes room conversation logs to the database.
0099: */
0100: private LogConversationTask logConversationTask;
0101: /**
0102: * the chat service's hostname
0103: */
0104: private String chatServiceName = null;
0105:
0106: /**
0107: * chatrooms managed by this manager, table: key room name (String); value ChatRoom
0108: */
0109: private Map<String, LocalMUCRoom> rooms = new ConcurrentHashMap<String, LocalMUCRoom>();
0110:
0111: /**
0112: * Chat users managed by this manager. This includes only users connected to this JVM.
0113: * That means that when running inside of a cluster each node will have its own manager
0114: * that in turn will keep its own list of locally connected.
0115: *
0116: * table: key user jid (XMPPAddress); value ChatUser
0117: */
0118: private Map<JID, LocalMUCUser> users = new ConcurrentHashMap<JID, LocalMUCUser>();
0119: private HistoryStrategy historyStrategy;
0120:
0121: private RoutingTable routingTable = null;
0122: /**
0123: * The packet router for the server.
0124: */
0125: private PacketRouter router = null;
0126: /**
0127: * The handler of packets with namespace jabber:iq:register for the server.
0128: */
0129: private IQMUCRegisterHandler registerHandler = null;
0130:
0131: /**
0132: * The handler of search requests ('jabber:iq:search' namespace).
0133: */
0134: private IQMUCSearchHandler searchHandler = null;
0135:
0136: /**
0137: * The total time all agents took to chat *
0138: */
0139: public long totalChatTime;
0140:
0141: /**
0142: * Timer to monitor chatroom participants. If they've been idle for too long, probe for
0143: * presence.
0144: */
0145: private Timer timer = new Timer("MUC cleanup");
0146:
0147: /**
0148: * Flag that indicates if the service should provide information about locked rooms when
0149: * handling service discovery requests.
0150: * Note: Setting this flag in false is not compliant with the spec. A user may try to join a
0151: * locked room thinking that the room doesn't exist because the user didn't discover it before.
0152: */
0153: private boolean allowToDiscoverLockedRooms = true;
0154:
0155: /**
0156: * Returns the permission policy for creating rooms. A true value means that not anyone can
0157: * create a room, only the JIDs listed in <code>allowedToCreate</code> are allowed to create
0158: * rooms.
0159: */
0160: private boolean roomCreationRestricted = false;
0161:
0162: /**
0163: * Bare jids of users that are allowed to create MUC rooms. An empty list means that anyone can
0164: * create a room.
0165: */
0166: private List<String> allowedToCreate = new CopyOnWriteArrayList<String>();
0167:
0168: /**
0169: * Bare jids of users that are system administrators of the MUC service. A sysadmin has the same
0170: * permissions as a room owner.
0171: */
0172: private List<String> sysadmins = new CopyOnWriteArrayList<String>();
0173:
0174: /**
0175: * Queue that holds the messages to log for the rooms that need to log their conversations.
0176: */
0177: private Queue<ConversationLogEntry> logQueue = new LinkedBlockingQueue<ConversationLogEntry>();
0178:
0179: /**
0180: * Max number of hours that a persistent room may be empty before the service removes the
0181: * room from memory. Unloaded rooms will exist in the database and may be loaded by a user
0182: * request. Default time limit is: 30 days.
0183: */
0184: private long emptyLimit = 30 * 24;
0185: /**
0186: * Task that removes rooms from memory that have been without activity for a period of time. A
0187: * room is considered without activity when no occupants are present in the room for a while.
0188: */
0189: private CleanupTask cleanupTask;
0190: /**
0191: * The time to elapse between each rooms cleanup. Default frequency is 60 minutes.
0192: */
0193: private static final long CLEANUP_FREQUENCY = 60 * 60 * 1000;
0194:
0195: /**
0196: * Total number of received messages in all rooms since the last reset. The counter
0197: * is reset each time the Statistic makes a sampling.
0198: */
0199: private AtomicInteger inMessages = new AtomicInteger(0);
0200: /**
0201: * Total number of broadcasted messages in all rooms since the last reset. The counter
0202: * is reset each time the Statistic makes a sampling.
0203: */
0204: private AtomicLong outMessages = new AtomicLong(0);
0205:
0206: /**
0207: * Flag that indicates if MUC service is enabled.
0208: */
0209: private boolean serviceEnabled = true;
0210:
0211: Collection<MUCEventListener> listeners = new ConcurrentLinkedQueue<MUCEventListener>();
0212:
0213: /**
0214: * Create a new group chat server.
0215: */
0216: public MultiUserChatServerImpl() {
0217: super ("Basic multi user chat server");
0218: historyStrategy = new HistoryStrategy(null);
0219: }
0220:
0221: public String getDescription() {
0222: return null;
0223: }
0224:
0225: public void process(Packet packet) throws UnauthorizedException,
0226: PacketException {
0227: // TODO Remove this method when moving MUC as a component and removing module code
0228: processPacket(packet);
0229: }
0230:
0231: public void processPacket(Packet packet) {
0232: if (!isServiceEnabled()) {
0233: return;
0234: }
0235: // The MUC service will receive all the packets whose domain matches the domain of the MUC
0236: // service. This means that, for instance, a disco request should be responded by the
0237: // service itself instead of relying on the server to handle the request.
0238: try {
0239: // Check if the packet is a disco request or a packet with namespace iq:register
0240: if (packet instanceof IQ) {
0241: if (process((IQ) packet)) {
0242: return;
0243: }
0244: }
0245: // The packet is a normal packet that should possibly be sent to the room
0246: JID receipient = packet.getTo();
0247: String roomName = receipient != null ? receipient.getNode()
0248: : null;
0249: getChatUser(packet.getFrom(), roomName).process(packet);
0250: } catch (Exception e) {
0251: Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
0252: }
0253: }
0254:
0255: /**
0256: * Returns true if the IQ packet was processed. This method should only process disco packets
0257: * as well as jabber:iq:register packets sent to the MUC service.
0258: *
0259: * @param iq the IQ packet to process.
0260: * @return true if the IQ packet was processed.
0261: */
0262: private boolean process(IQ iq) {
0263: Element childElement = iq.getChildElement();
0264: String namespace = null;
0265: // Ignore IQs of type ERROR
0266: if (IQ.Type.error == iq.getType()) {
0267: return false;
0268: }
0269: if (iq.getTo().getResource() != null) {
0270: // Ignore IQ packets sent to room occupants
0271: return false;
0272: }
0273: if (childElement != null) {
0274: namespace = childElement.getNamespaceURI();
0275: }
0276: if ("jabber:iq:register".equals(namespace)) {
0277: IQ reply = registerHandler.handleIQ(iq);
0278: router.route(reply);
0279: } else if ("jabber:iq:search".equals(namespace)) {
0280: IQ reply = searchHandler.handleIQ(iq);
0281: router.route(reply);
0282: } else if ("http://jabber.org/protocol/disco#info"
0283: .equals(namespace)) {
0284: // TODO MUC should have an IQDiscoInfoHandler of its own when MUC becomes
0285: // a component
0286: IQ reply = XMPPServer.getInstance().getIQDiscoInfoHandler()
0287: .handleIQ(iq);
0288: router.route(reply);
0289: } else if ("http://jabber.org/protocol/disco#items"
0290: .equals(namespace)) {
0291: // TODO MUC should have an IQDiscoItemsHandler of its own when MUC becomes
0292: // a component
0293: IQ reply = XMPPServer.getInstance()
0294: .getIQDiscoItemsHandler().handleIQ(iq);
0295: router.route(reply);
0296: } else {
0297: return false;
0298: }
0299: return true;
0300: }
0301:
0302: public void initialize(JID jid, ComponentManager componentManager) {
0303:
0304: }
0305:
0306: public void shutdown() {
0307:
0308: }
0309:
0310: public String getServiceDomain() {
0311: return chatServiceName + "."
0312: + XMPPServer.getInstance().getServerInfo().getName();
0313: }
0314:
0315: public JID getAddress() {
0316: return new JID(null, getServiceDomain(), null, true);
0317: }
0318:
0319: /**
0320: * Probes the presence of any user who's last packet was sent more than 5 minute ago.
0321: */
0322: private class UserTimeoutTask extends TimerTask {
0323: /**
0324: * Remove any user that has been idle for longer than the user timeout time.
0325: */
0326: public void run() {
0327: checkForTimedOutUsers();
0328: }
0329: }
0330:
0331: private void checkForTimedOutUsers() {
0332: final long deadline = System.currentTimeMillis() - user_idle;
0333: for (LocalMUCUser user : users.values()) {
0334: try {
0335: // If user is not present in any room then remove the user from
0336: // the list of users
0337: if (!user.isJoined()) {
0338: removeUser(user.getAddress());
0339: continue;
0340: }
0341: // Do nothing if this feature is disabled (i.e USER_IDLE equals -1)
0342: if (user_idle == -1) {
0343: return;
0344: }
0345: if (user.getLastPacketTime() < deadline) {
0346: // Kick the user from all the rooms that he/she had previuosly joined
0347: MUCRoom room;
0348: Presence kickedPresence;
0349: for (LocalMUCRole role : user.getRoles()) {
0350: room = role.getChatRoom();
0351: try {
0352: kickedPresence = room.kickOccupant(user
0353: .getAddress(), null, null);
0354: // Send the updated presence to the room occupants
0355: room.send(kickedPresence);
0356: } catch (NotAllowedException e) {
0357: // Do nothing since we cannot kick owners or admins
0358: }
0359: }
0360: }
0361: } catch (Throwable e) {
0362: Log.error(
0363: LocaleUtils.getLocalizedString("admin.error"),
0364: e);
0365: }
0366: }
0367: }
0368:
0369: /**
0370: * Logs the conversation of the rooms that have this feature enabled.
0371: */
0372: private class LogConversationTask extends TimerTask {
0373: public void run() {
0374: try {
0375: logConversation();
0376: } catch (Throwable e) {
0377: Log.error(
0378: LocaleUtils.getLocalizedString("admin.error"),
0379: e);
0380: }
0381: }
0382: }
0383:
0384: private void logConversation() {
0385: ConversationLogEntry entry;
0386: boolean success;
0387: for (int index = 0; index <= log_batch_size
0388: && !logQueue.isEmpty(); index++) {
0389: entry = logQueue.poll();
0390: if (entry != null) {
0391: success = MUCPersistenceManager
0392: .saveConversationLogEntry(entry);
0393: if (!success) {
0394: logQueue.add(entry);
0395: }
0396: }
0397: }
0398: }
0399:
0400: /**
0401: * Logs all the remaining conversation log entries to the database. Use this method to force
0402: * saving all the conversation log entries before the service becomes unavailable.
0403: */
0404: private void logAllConversation() {
0405: ConversationLogEntry entry;
0406: while (!logQueue.isEmpty()) {
0407: entry = logQueue.poll();
0408: if (entry != null) {
0409: MUCPersistenceManager.saveConversationLogEntry(entry);
0410: }
0411: }
0412: }
0413:
0414: /**
0415: * Removes from memory rooms that have been without activity for a period of time. A room is
0416: * considered without activity when no occupants are present in the room for a while.
0417: */
0418: private class CleanupTask extends TimerTask {
0419: public void run() {
0420: if (ClusterManager.isClusteringStarted()
0421: && !ClusterManager.isSeniorClusterMember()) {
0422: // Do nothing if we are in a cluster and this JVM is not the senior cluster member
0423: return;
0424: }
0425: try {
0426: cleanupRooms();
0427: } catch (Throwable e) {
0428: Log.error(
0429: LocaleUtils.getLocalizedString("admin.error"),
0430: e);
0431: }
0432: }
0433: }
0434:
0435: private void cleanupRooms() {
0436: for (MUCRoom room : rooms.values()) {
0437: if (room.getEmptyDate() != null
0438: && room.getEmptyDate().before(getCleanupDate())) {
0439: removeChatRoom(room.getName());
0440: }
0441: }
0442: }
0443:
0444: public MUCRoom getChatRoom(String roomName, JID userjid)
0445: throws NotAllowedException {
0446: LocalMUCRoom room;
0447: boolean loaded = false;
0448: boolean created = false;
0449: synchronized (roomName.intern()) {
0450: room = rooms.get(roomName);
0451: if (room == null) {
0452: room = new LocalMUCRoom(this , roomName, router);
0453: // If the room is persistent load the configuration values from the DB
0454: try {
0455: // Try to load the room's configuration from the database (if the room is
0456: // persistent but was added to the DB after the server was started up or the
0457: // room may be an old room that was not present in memory)
0458: MUCPersistenceManager
0459: .loadFromDB((LocalMUCRoom) room);
0460: loaded = true;
0461: } catch (IllegalArgumentException e) {
0462: // The room does not exist so check for creation permissions
0463: // Room creation is always allowed for sysadmin
0464: if (isRoomCreationRestricted()
0465: && !sysadmins.contains(userjid.toBareJID())) {
0466: // The room creation is only allowed for certain JIDs
0467: if (!allowedToCreate.contains(userjid
0468: .toBareJID())) {
0469: // The user is not in the list of allowed JIDs to create a room so raise
0470: // an exception
0471: throw new NotAllowedException();
0472: }
0473: }
0474: room.addFirstOwner(userjid.toBareJID());
0475: created = true;
0476: }
0477: rooms.put(roomName, room);
0478: }
0479: }
0480: if (created) {
0481: // Fire event that a new room has been created
0482: for (MUCEventListener listener : listeners) {
0483: listener.roomCreated(room.getRole().getRoleAddress());
0484: }
0485: }
0486: if (loaded || created) {
0487: // Notify other cluster nodes that a new room is available
0488: CacheFactory.doClusterTask(new RoomAvailableEvent(room));
0489: for (MUCRole role : room.getOccupants()) {
0490: if (role instanceof LocalMUCRole) {
0491: CacheFactory.doClusterTask(new OccupantAddedEvent(
0492: room, role));
0493: }
0494: }
0495: }
0496: return room;
0497: }
0498:
0499: public MUCRoom getChatRoom(String roomName) {
0500: boolean loaded = false;
0501: LocalMUCRoom room = rooms.get(roomName);
0502: if (room == null) {
0503: // Check if the room exists in the database and was not present in memory
0504: synchronized (roomName.intern()) {
0505: room = rooms.get(roomName);
0506: if (room == null) {
0507: room = new LocalMUCRoom(this , roomName, router);
0508: // If the room is persistent load the configuration values from the DB
0509: try {
0510: // Try to load the room's configuration from the database (if the room is
0511: // persistent but was added to the DB after the server was started up or the
0512: // room may be an old room that was not present in memory)
0513: MUCPersistenceManager
0514: .loadFromDB((LocalMUCRoom) room);
0515: loaded = true;
0516: rooms.put(roomName, room);
0517: } catch (IllegalArgumentException e) {
0518: // The room does not exist so do nothing
0519: room = null;
0520: }
0521: }
0522: }
0523: }
0524: if (loaded) {
0525: // Notify other cluster nodes that a new room is available
0526: CacheFactory.doClusterTask(new RoomAvailableEvent(room));
0527: }
0528: return room;
0529: }
0530:
0531: public List<MUCRoom> getChatRooms() {
0532: return new ArrayList<MUCRoom>(rooms.values());
0533: }
0534:
0535: public boolean hasChatRoom(String roomName) {
0536: return getChatRoom(roomName) != null;
0537: }
0538:
0539: public void removeChatRoom(String roomName) {
0540: removeChatRoom(roomName, true);
0541: }
0542:
0543: /**
0544: * Notification message indicating that the specified chat room was
0545: * removed from some other cluster member.
0546: *
0547: * @param roomName the name of the room removed from the cluster.
0548: */
0549: public void chatRoomRemoved(String roomName) {
0550: removeChatRoom(roomName, false);
0551: }
0552:
0553: /**
0554: * Notification message indicating that a chat room has been created
0555: * in another cluster member.
0556: *
0557: * @param room the created room in another cluster node.
0558: */
0559: public void chatRoomAdded(LocalMUCRoom room) {
0560: rooms.put(room.getName(), room);
0561: }
0562:
0563: private void removeChatRoom(String roomName, boolean notify) {
0564: MUCRoom room = rooms.remove(roomName);
0565: if (room != null) {
0566: totalChatTime += room.getChatLength();
0567: if (notify) {
0568: // Notify other cluster nodes that a room has been removed
0569: CacheFactory.doClusterTask(new RoomRemovedEvent(
0570: roomName));
0571: }
0572: }
0573: }
0574:
0575: public String getServiceName() {
0576: return chatServiceName;
0577: }
0578:
0579: public HistoryStrategy getHistoryStrategy() {
0580: return historyStrategy;
0581: }
0582:
0583: /**
0584: * Removes a user from all chat rooms.
0585: *
0586: * @param jabberID The user's normal jid, not the chat nickname jid.
0587: */
0588: private void removeUser(JID jabberID) {
0589: LocalMUCUser user = users.remove(jabberID);
0590: if (user != null) {
0591: for (LocalMUCRole role : user.getRoles()) {
0592: try {
0593: role.getChatRoom().leaveRoom(role);
0594: } catch (Exception e) {
0595: Log.error(e);
0596: }
0597: }
0598: }
0599: }
0600:
0601: /**
0602: * Obtain a chat user by XMPPAddress. Only returns users that are connected to this JVM.
0603: *
0604: * @param userjid The XMPPAddress of the user.
0605: * @param roomName name of the room to receive the packet.
0606: * @return The chatuser corresponding to that XMPPAddress.
0607: */
0608: private MUCUser getChatUser(JID userjid, String roomName) {
0609: if (router == null) {
0610: throw new IllegalStateException("Not initialized");
0611: }
0612: LocalMUCUser user;
0613: synchronized (userjid.toString().intern()) {
0614: user = users.get(userjid);
0615: if (user == null) {
0616: if (roomName != null) {
0617: // Check if the JID belong to a user hosted in another cluster node
0618: LocalMUCRoom localMUCRoom = rooms.get(roomName);
0619: if (localMUCRoom != null) {
0620: MUCRole occupant = localMUCRoom
0621: .getOccupantByFullJID(userjid);
0622: if (occupant != null && !occupant.isLocal()) {
0623: return new RemoteMUCUser(userjid,
0624: localMUCRoom);
0625: }
0626: }
0627: }
0628: user = new LocalMUCUser(this , router, userjid);
0629: users.put(userjid, user);
0630: }
0631: }
0632: return user;
0633: }
0634:
0635: public Collection<MUCRole> getMUCRoles(JID user) {
0636: List<MUCRole> userRoles = new ArrayList<MUCRole>();
0637: for (LocalMUCRoom room : rooms.values()) {
0638: MUCRole role = room.getOccupantByFullJID(user);
0639: if (role != null) {
0640: userRoles.add(role);
0641: }
0642: }
0643: return userRoles;
0644: }
0645:
0646: public void setServiceName(String name) {
0647: JiveGlobals.setProperty("xmpp.muc.service", name);
0648: }
0649:
0650: /**
0651: * Returns the limit date after which rooms without activity will be removed from memory.
0652: *
0653: * @return the limit date after which rooms without activity will be removed from memory.
0654: */
0655: private Date getCleanupDate() {
0656: return new Date(System.currentTimeMillis()
0657: - (emptyLimit * 3600000));
0658: }
0659:
0660: public void setKickIdleUsersTimeout(int timeout) {
0661: if (this .user_timeout == timeout) {
0662: return;
0663: }
0664: // Cancel the existing task because the timeout has changed
0665: if (userTimeoutTask != null) {
0666: userTimeoutTask.cancel();
0667: }
0668: this .user_timeout = timeout;
0669: // Create a new task and schedule it with the new timeout
0670: userTimeoutTask = new UserTimeoutTask();
0671: timer.schedule(userTimeoutTask, user_timeout, user_timeout);
0672: // Set the new property value
0673: JiveGlobals.setProperty("xmpp.muc.tasks.user.timeout", Integer
0674: .toString(timeout));
0675: }
0676:
0677: public int getKickIdleUsersTimeout() {
0678: return user_timeout;
0679: }
0680:
0681: public void setUserIdleTime(int idleTime) {
0682: if (this .user_idle == idleTime) {
0683: return;
0684: }
0685: this .user_idle = idleTime;
0686: // Set the new property value
0687: JiveGlobals.setProperty("xmpp.muc.tasks.user.idle", Integer
0688: .toString(idleTime));
0689: }
0690:
0691: public int getUserIdleTime() {
0692: return user_idle;
0693: }
0694:
0695: public void setLogConversationsTimeout(int timeout) {
0696: if (this .log_timeout == timeout) {
0697: return;
0698: }
0699: // Cancel the existing task because the timeout has changed
0700: if (logConversationTask != null) {
0701: logConversationTask.cancel();
0702: }
0703: this .log_timeout = timeout;
0704: // Create a new task and schedule it with the new timeout
0705: logConversationTask = new LogConversationTask();
0706: timer.schedule(logConversationTask, log_timeout, log_timeout);
0707: // Set the new property value
0708: JiveGlobals.setProperty("xmpp.muc.tasks.log.timeout", Integer
0709: .toString(timeout));
0710: }
0711:
0712: public int getLogConversationsTimeout() {
0713: return log_timeout;
0714: }
0715:
0716: public void setLogConversationBatchSize(int size) {
0717: if (this .log_batch_size == size) {
0718: return;
0719: }
0720: this .log_batch_size = size;
0721: // Set the new property value
0722: JiveGlobals.setProperty("xmpp.muc.tasks.log.batchsize", Integer
0723: .toString(size));
0724: }
0725:
0726: public int getLogConversationBatchSize() {
0727: return log_batch_size;
0728: }
0729:
0730: public Collection<String> getUsersAllowedToCreate() {
0731: return allowedToCreate;
0732: }
0733:
0734: public Collection<String> getSysadmins() {
0735: return sysadmins;
0736: }
0737:
0738: public void addSysadmin(String userJID) {
0739: sysadmins.add(userJID.trim().toLowerCase());
0740: // CopyOnWriteArray does not allow sorting, so do sorting in temp list.
0741: ArrayList<String> tempList = new ArrayList<String>(sysadmins);
0742: Collections.sort(tempList);
0743: sysadmins = new CopyOnWriteArrayList<String>(tempList);
0744: // Update the config.
0745: String[] jids = new String[sysadmins.size()];
0746: jids = sysadmins.toArray(jids);
0747: JiveGlobals.setProperty("xmpp.muc.sysadmin.jid",
0748: fromArray(jids));
0749: }
0750:
0751: public void removeSysadmin(String userJID) {
0752: sysadmins.remove(userJID.trim().toLowerCase());
0753: // Update the config.
0754: String[] jids = new String[sysadmins.size()];
0755: jids = sysadmins.toArray(jids);
0756: JiveGlobals.setProperty("xmpp.muc.sysadmin.jid",
0757: fromArray(jids));
0758: }
0759:
0760: /**
0761: * Returns the flag that indicates if the service should provide information about locked rooms
0762: * when handling service discovery requests.
0763: *
0764: * @return true if the service should provide information about locked rooms.
0765: */
0766: public boolean isAllowToDiscoverLockedRooms() {
0767: return allowToDiscoverLockedRooms;
0768: }
0769:
0770: /**
0771: * Sets the flag that indicates if the service should provide information about locked rooms
0772: * when handling service discovery requests.
0773: * Note: Setting this flag in false is not compliant with the spec. A user may try to join a
0774: * locked room thinking that the room doesn't exist because the user didn't discover it before.
0775: *
0776: * @param allowToDiscoverLockedRooms if the service should provide information about locked
0777: * rooms.
0778: */
0779: public void setAllowToDiscoverLockedRooms(
0780: boolean allowToDiscoverLockedRooms) {
0781: this .allowToDiscoverLockedRooms = allowToDiscoverLockedRooms;
0782: JiveGlobals.setProperty("xmpp.muc.discover.locked", Boolean
0783: .toString(allowToDiscoverLockedRooms));
0784: }
0785:
0786: public boolean isRoomCreationRestricted() {
0787: return roomCreationRestricted;
0788: }
0789:
0790: public void setRoomCreationRestricted(boolean roomCreationRestricted) {
0791: this .roomCreationRestricted = roomCreationRestricted;
0792: JiveGlobals.setProperty("xmpp.muc.create.anyone", Boolean
0793: .toString(roomCreationRestricted));
0794: }
0795:
0796: public void addUserAllowedToCreate(String userJID) {
0797: // Update the list of allowed JIDs to create MUC rooms. Since we are updating the instance
0798: // variable there is no need to restart the service
0799: allowedToCreate.add(userJID.trim().toLowerCase());
0800: // CopyOnWriteArray does not allow sorting, so do sorting in temp list.
0801: ArrayList<String> tempList = new ArrayList<String>(
0802: allowedToCreate);
0803: Collections.sort(tempList);
0804: allowedToCreate = new CopyOnWriteArrayList<String>(tempList);
0805: // Update the config.
0806: String[] jids = new String[allowedToCreate.size()];
0807: jids = allowedToCreate.toArray(jids);
0808: JiveGlobals.setProperty("xmpp.muc.create.jid", fromArray(jids));
0809: }
0810:
0811: public void removeUserAllowedToCreate(String userJID) {
0812: // Update the list of allowed JIDs to create MUC rooms. Since we are updating the instance
0813: // variable there is no need to restart the service
0814: allowedToCreate.remove(userJID.trim().toLowerCase());
0815: // Update the config.
0816: String[] jids = new String[allowedToCreate.size()];
0817: jids = allowedToCreate.toArray(jids);
0818: JiveGlobals.setProperty("xmpp.muc.create.jid", fromArray(jids));
0819: }
0820:
0821: public void initialize(XMPPServer server) {
0822: super .initialize(server);
0823:
0824: serviceEnabled = JiveGlobals.getBooleanProperty(
0825: "xmpp.muc.enabled", true);
0826: chatServiceName = JiveGlobals.getProperty("xmpp.muc.service");
0827: // Trigger the strategy to load itself from the context
0828: historyStrategy.setContext("xmpp.muc.history");
0829: // Load the list of JIDs that are sysadmins of the MUC service
0830: String property = JiveGlobals
0831: .getProperty("xmpp.muc.sysadmin.jid");
0832: String[] jids;
0833: if (property != null) {
0834: jids = property.split(",");
0835: for (String jid : jids) {
0836: sysadmins.add(jid.trim().toLowerCase());
0837: }
0838: }
0839: allowToDiscoverLockedRooms = Boolean.parseBoolean(JiveGlobals
0840: .getProperty("xmpp.muc.discover.locked", "true"));
0841: roomCreationRestricted = Boolean.parseBoolean(JiveGlobals
0842: .getProperty("xmpp.muc.create.anyone", "false"));
0843: // Load the list of JIDs that are allowed to create a MUC room
0844: property = JiveGlobals.getProperty("xmpp.muc.create.jid");
0845: if (property != null) {
0846: jids = property.split(",");
0847: for (String jid : jids) {
0848: allowedToCreate.add(jid.trim().toLowerCase());
0849: }
0850: }
0851: String value = JiveGlobals
0852: .getProperty("xmpp.muc.tasks.user.timeout");
0853: if (value != null) {
0854: try {
0855: user_timeout = Integer.parseInt(value);
0856: } catch (NumberFormatException e) {
0857: Log
0858: .error(
0859: "Wrong number format of property xmpp.muc.tasks.user.timeout",
0860: e);
0861: }
0862: }
0863: value = JiveGlobals.getProperty("xmpp.muc.tasks.user.idle");
0864: if (value != null) {
0865: try {
0866: user_idle = Integer.parseInt(value);
0867: } catch (NumberFormatException e) {
0868: Log
0869: .error(
0870: "Wrong number format of property xmpp.muc.tasks.user.idle",
0871: e);
0872: }
0873: }
0874: value = JiveGlobals.getProperty("xmpp.muc.tasks.log.timeout");
0875: if (value != null) {
0876: try {
0877: log_timeout = Integer.parseInt(value);
0878: } catch (NumberFormatException e) {
0879: Log
0880: .error(
0881: "Wrong number format of property xmpp.muc.tasks.log.timeout",
0882: e);
0883: }
0884: }
0885: value = JiveGlobals.getProperty("xmpp.muc.tasks.log.batchsize");
0886: if (value != null) {
0887: try {
0888: log_batch_size = Integer.parseInt(value);
0889: } catch (NumberFormatException e) {
0890: Log
0891: .error(
0892: "Wrong number format of property xmpp.muc.tasks.log.batchsize",
0893: e);
0894: }
0895: }
0896: value = JiveGlobals.getProperty("xmpp.muc.unload.empty_days");
0897: if (value != null) {
0898: try {
0899: emptyLimit = Integer.parseInt(value) * 24;
0900: } catch (NumberFormatException e) {
0901: Log
0902: .error(
0903: "Wrong number format of property xmpp.muc.unload.empty_days",
0904: e);
0905: }
0906: }
0907: if (chatServiceName == null) {
0908: chatServiceName = "conference";
0909: }
0910: // Run through the users every 5 minutes after a 5 minutes server startup delay (default
0911: // values)
0912: userTimeoutTask = new UserTimeoutTask();
0913: timer.schedule(userTimeoutTask, user_timeout, user_timeout);
0914: // Log the room conversations every 5 minutes after a 5 minutes server startup delay
0915: // (default values)
0916: logConversationTask = new LogConversationTask();
0917: timer.schedule(logConversationTask, log_timeout, log_timeout);
0918: // Remove unused rooms from memory
0919: cleanupTask = new CleanupTask();
0920: timer.schedule(cleanupTask, CLEANUP_FREQUENCY,
0921: CLEANUP_FREQUENCY);
0922:
0923: routingTable = server.getRoutingTable();
0924: router = server.getPacketRouter();
0925: // Configure the handler of iq:register packets
0926: registerHandler = new IQMUCRegisterHandler(this );
0927: // Configure the handler of jabber:iq:search packets
0928: searchHandler = new IQMUCSearchHandler(this );
0929: // Listen to cluster events
0930: ClusterManager.addListener(this );
0931: }
0932:
0933: public void start() {
0934: super .start();
0935: // Add the route to this service
0936: routingTable.addComponentRoute(getAddress(), this );
0937: ArrayList<String> params = new ArrayList<String>();
0938: params.clear();
0939: params.add(getServiceDomain());
0940: Log.info(LocaleUtils.getLocalizedString("startup.starting.muc",
0941: params));
0942: // Load all the persistent rooms to memory
0943: for (LocalMUCRoom room : MUCPersistenceManager.loadRoomsFromDB(
0944: this , this .getCleanupDate(), router)) {
0945: rooms.put(room.getName().toLowerCase(), room);
0946: }
0947: // Add statistics
0948: addTotalRoomStats();
0949: addTotalOccupantsStats();
0950: addTotalConnectedUsers();
0951: addNumberIncomingMessages();
0952: addNumberOutgoingMessages();
0953: }
0954:
0955: public void stop() {
0956: super .stop();
0957: // Remove the route to this service
0958: routingTable.removeComponentRoute(getAddress());
0959: timer.cancel();
0960: logAllConversation();
0961: // Remove the statistics.
0962: StatisticsManager.getInstance().removeStatistic(roomsStatKey);
0963: StatisticsManager.getInstance().removeStatistic(
0964: occupantsStatKey);
0965: StatisticsManager.getInstance().removeStatistic(usersStatKey);
0966: StatisticsManager.getInstance()
0967: .removeStatistic(incomingStatKey);
0968: StatisticsManager.getInstance()
0969: .removeStatistic(outgoingStatKey);
0970:
0971: }
0972:
0973: public void enableService(boolean enabled, boolean persistent) {
0974: if (isServiceEnabled() == enabled) {
0975: // Do nothing if the service status has not changed
0976: return;
0977: }
0978: XMPPServer server = XMPPServer.getInstance();
0979: if (!enabled) {
0980: // Disable disco information
0981: server.getIQDiscoItemsHandler().removeServerItemsProvider(
0982: this );
0983: // Stop the service/module
0984: stop();
0985: }
0986: if (persistent) {
0987: JiveGlobals.setProperty("xmpp.muc.enabled", Boolean
0988: .toString(enabled));
0989: }
0990: serviceEnabled = enabled;
0991: if (enabled) {
0992: // Start the service/module
0993: start();
0994: // Enable disco information
0995: server.getIQDiscoItemsHandler()
0996: .addServerItemsProvider(this );
0997: }
0998: }
0999:
1000: public boolean isServiceEnabled() {
1001: return serviceEnabled;
1002: }
1003:
1004: public long getTotalChatTime() {
1005: return totalChatTime;
1006: }
1007:
1008: /**
1009: * Retuns the number of existing rooms in the server (i.e. persistent or not,
1010: * in memory or not).
1011: *
1012: * @return the number of existing rooms in the server.
1013: */
1014: public int getNumberChatRooms() {
1015: return rooms.size();
1016: }
1017:
1018: /**
1019: * Retuns the total number of occupants in all rooms in the server.
1020: *
1021: * @param onlyLocal true if only users connected to this JVM will be considered. Otherwise count cluster wise.
1022: * @return the number of existing rooms in the server.
1023: */
1024: public int getNumberConnectedUsers(boolean onlyLocal) {
1025: int total = 0;
1026: for (LocalMUCUser user : users.values()) {
1027: if (user.isJoined()) {
1028: total = total + 1;
1029: }
1030: }
1031: // Add users from remote cluster nodes
1032: if (!onlyLocal) {
1033: Collection<Object> results = CacheFactory
1034: .doSynchronousClusterTask(
1035: new GetNumberConnectedUsers(), false);
1036: for (Object result : results) {
1037: if (result == null) {
1038: continue;
1039: }
1040: total = total + (Integer) result;
1041: }
1042: }
1043: return total;
1044: }
1045:
1046: /**
1047: * Retuns the total number of users that have joined in all rooms in the server.
1048: *
1049: * @return the number of existing rooms in the server.
1050: */
1051: public int getNumberRoomOccupants() {
1052: int total = 0;
1053: for (MUCRoom room : rooms.values()) {
1054: total = total + room.getOccupantsCount();
1055: }
1056: return total;
1057: }
1058:
1059: public void logConversation(MUCRoom room, Message message,
1060: JID sender) {
1061: // Only log messages that have a subject or body. Otherwise ignore it.
1062: if (message.getSubject() != null || message.getBody() != null) {
1063: logQueue.add(new ConversationLogEntry(new Date(), room,
1064: message, sender));
1065: }
1066: }
1067:
1068: public void messageBroadcastedTo(int numOccupants) {
1069: // Increment counter of received messages that where broadcasted by one
1070: inMessages.incrementAndGet();
1071: // Increment counter of outgoing messages with the number of room occupants
1072: // that received the message
1073: outMessages.addAndGet(numOccupants);
1074: }
1075:
1076: public void joinedCluster() {
1077: if (isServiceEnabled()) {
1078: if (!ClusterManager.isSeniorClusterMember()) {
1079: // Get transient rooms and persistent rooms with occupants from senior
1080: // cluster member and merge with local ones. If room configuration was
1081: // changed in both places then latest configuration will be kept
1082: List<RoomInfo> result = (List<RoomInfo>) CacheFactory
1083: .doSynchronousClusterTask(
1084: new SeniorMemberRoomsRequest(),
1085: ClusterManager.getSeniorClusterMember()
1086: .toByteArray());
1087: if (result != null) {
1088: for (RoomInfo roomInfo : result) {
1089: LocalMUCRoom remoteRoom = roomInfo.getRoom();
1090: LocalMUCRoom localRoom = rooms.get(remoteRoom
1091: .getName());
1092: if (localRoom == null) {
1093: // Create local room with remote information
1094: localRoom = remoteRoom;
1095: rooms.put(remoteRoom.getName(), localRoom);
1096: } else {
1097: // Update local room with remote information
1098: localRoom.updateConfiguration(remoteRoom);
1099: }
1100: // Add remote occupants to local room
1101: // TODO Handle conflict of nicknames
1102: for (OccupantAddedEvent event : roomInfo
1103: .getOccupants()) {
1104: event.setSendPresence(true);
1105: event.run();
1106: }
1107: }
1108: }
1109: }
1110: }
1111: }
1112:
1113: public void joinedCluster(byte[] nodeID) {
1114: if (isServiceEnabled()) {
1115: List<RoomInfo> result = (List<RoomInfo>) CacheFactory
1116: .doSynchronousClusterTask(
1117: new GetNewMemberRoomsRequest(), nodeID);
1118: if (result != null) {
1119: for (RoomInfo roomInfo : result) {
1120: LocalMUCRoom remoteRoom = roomInfo.getRoom();
1121: LocalMUCRoom localRoom = rooms.get(remoteRoom
1122: .getName());
1123: if (localRoom == null) {
1124: // Create local room with remote information
1125: localRoom = remoteRoom;
1126: rooms.put(remoteRoom.getName(), localRoom);
1127: }
1128: // Add remote occupants to local room
1129: for (OccupantAddedEvent event : roomInfo
1130: .getOccupants()) {
1131: event.setSendPresence(true);
1132: event.run();
1133: }
1134: }
1135: }
1136: }
1137: }
1138:
1139: public void leftCluster() {
1140: // Do nothing. An unavailable presence will be created for occupants hosted in other cluster nodes.
1141: }
1142:
1143: public void leftCluster(byte[] nodeID) {
1144: // Do nothing. An unavailable presence will be created for occupants hosted in the leaving cluster node.
1145: }
1146:
1147: public void markedAsSeniorClusterMember() {
1148: // Do nothing
1149: }
1150:
1151: public Iterator<DiscoServerItem> getItems() {
1152: // Check if the service is disabled. Info is not available when
1153: // disabled.
1154: if (!isServiceEnabled()) {
1155: return null;
1156: }
1157:
1158: final ArrayList<DiscoServerItem> items = new ArrayList<DiscoServerItem>();
1159: final String name;
1160: // Check if there is a system property that overrides the default value
1161: String serviceName = JiveGlobals
1162: .getProperty("muc.service-name");
1163: if (serviceName != null && serviceName.trim().length() > 0) {
1164: name = serviceName;
1165: } else {
1166: // Return the default service name based on the current locale
1167: name = LocaleUtils.getLocalizedString("muc.service-name");
1168: }
1169:
1170: final DiscoServerItem item = new DiscoServerItem(new JID(
1171: getServiceDomain()), name, null, null, this , this );
1172: items.add(item);
1173: return items.iterator();
1174: }
1175:
1176: public Iterator<Element> getIdentities(String name, String node,
1177: JID senderJID) {
1178: ArrayList<Element> identities = new ArrayList<Element>();
1179: if (name == null && node == null) {
1180: // Answer the identity of the MUC service
1181: Element identity = DocumentHelper.createElement("identity");
1182: identity.addAttribute("category", "conference");
1183: identity.addAttribute("name", "Public Chatrooms");
1184: identity.addAttribute("type", "text");
1185:
1186: identities.add(identity);
1187:
1188: Element searchId = DocumentHelper.createElement("identity");
1189: searchId.addAttribute("category", "directory");
1190: searchId.addAttribute("name", "Public Chatroom Search");
1191: searchId.addAttribute("type", "chatroom");
1192: identities.add(searchId);
1193: } else if (name != null && node == null) {
1194: // Answer the identity of a given room
1195: MUCRoom room = getChatRoom(name);
1196: if (room != null && canDiscoverRoom(room)) {
1197: Element identity = DocumentHelper
1198: .createElement("identity");
1199: identity.addAttribute("category", "conference");
1200: identity.addAttribute("name", room
1201: .getNaturalLanguageName());
1202: identity.addAttribute("type", "text");
1203:
1204: identities.add(identity);
1205: }
1206: } else if (name != null && "x-roomuser-item".equals(node)) {
1207: // Answer reserved nickname for the sender of the disco request in the requested room
1208: MUCRoom room = getChatRoom(name);
1209: if (room != null) {
1210: String reservedNick = room
1211: .getReservedNickname(senderJID.toBareJID());
1212: if (reservedNick != null) {
1213: Element identity = DocumentHelper
1214: .createElement("identity");
1215: identity.addAttribute("category", "conference");
1216: identity.addAttribute("name", reservedNick);
1217: identity.addAttribute("type", "text");
1218:
1219: identities.add(identity);
1220: }
1221: }
1222: }
1223: return identities.iterator();
1224: }
1225:
1226: public Iterator<String> getFeatures(String name, String node,
1227: JID senderJID) {
1228: ArrayList<String> features = new ArrayList<String>();
1229: if (name == null && node == null) {
1230: // Answer the features of the MUC service
1231: features.add("http://jabber.org/protocol/muc");
1232: features.add("http://jabber.org/protocol/disco#info");
1233: features.add("http://jabber.org/protocol/disco#items");
1234: features.add("jabber:iq:search");
1235: features.add(ResultSet.NAMESPACE_RESULT_SET_MANAGEMENT);
1236: } else if (name != null && node == null) {
1237: // Answer the features of a given room
1238: MUCRoom room = getChatRoom(name);
1239: if (room != null && canDiscoverRoom(room)) {
1240: features.add("http://jabber.org/protocol/muc");
1241: // Always add public since only public rooms can be discovered
1242: features.add("muc_public");
1243: if (room.isMembersOnly()) {
1244: features.add("muc_membersonly");
1245: } else {
1246: features.add("muc_open");
1247: }
1248: if (room.isModerated()) {
1249: features.add("muc_moderated");
1250: } else {
1251: features.add("muc_unmoderated");
1252: }
1253: if (room.canAnyoneDiscoverJID()) {
1254: features.add("muc_nonanonymous");
1255: } else {
1256: features.add("muc_semianonymous");
1257: }
1258: if (room.isPasswordProtected()) {
1259: features.add("muc_passwordprotected");
1260: } else {
1261: features.add("muc_unsecured");
1262: }
1263: if (room.isPersistent()) {
1264: features.add("muc_persistent");
1265: } else {
1266: features.add("muc_temporary");
1267: }
1268: }
1269: }
1270: return features.iterator();
1271: }
1272:
1273: public XDataFormImpl getExtendedInfo(String name, String node,
1274: JID senderJID) {
1275: if (name != null && node == null) {
1276: // Answer the extended info of a given room
1277: MUCRoom room = getChatRoom(name);
1278: if (room != null && canDiscoverRoom(room)) {
1279: XDataFormImpl dataForm = new XDataFormImpl(
1280: DataForm.TYPE_RESULT);
1281:
1282: XFormFieldImpl field = new XFormFieldImpl("FORM_TYPE");
1283: field.setType(FormField.TYPE_HIDDEN);
1284: field
1285: .addValue("http://jabber.org/protocol/muc#roominfo");
1286: dataForm.addField(field);
1287:
1288: field = new XFormFieldImpl("muc#roominfo_description");
1289: field.setLabel(LocaleUtils
1290: .getLocalizedString("muc.extended.info.desc"));
1291: field.addValue(room.getDescription());
1292: dataForm.addField(field);
1293:
1294: field = new XFormFieldImpl("muc#roominfo_subject");
1295: field
1296: .setLabel(LocaleUtils
1297: .getLocalizedString("muc.extended.info.subject"));
1298: field.addValue(room.getSubject());
1299: dataForm.addField(field);
1300:
1301: field = new XFormFieldImpl("muc#roominfo_occupants");
1302: field
1303: .setLabel(LocaleUtils
1304: .getLocalizedString("muc.extended.info.occupants"));
1305: field.addValue(Integer.toString(room
1306: .getOccupantsCount()));
1307: dataForm.addField(field);
1308:
1309: /*field = new XFormFieldImpl("muc#roominfo_lang");
1310: field.setLabel(LocaleUtils.getLocalizedString("muc.extended.info.language"));
1311: field.addValue(room.getLanguage());
1312: dataForm.addField(field);*/
1313:
1314: field = new XFormFieldImpl(
1315: "x-muc#roominfo_creationdate");
1316: field
1317: .setLabel(LocaleUtils
1318: .getLocalizedString("muc.extended.info.creationdate"));
1319: field.addValue(dateFormatter.format(room
1320: .getCreationDate()));
1321: dataForm.addField(field);
1322:
1323: return dataForm;
1324: }
1325: }
1326: return null;
1327: }
1328:
1329: public boolean hasInfo(String name, String node, JID senderJID) {
1330: // Check if the service is disabled. Info is not available when disabled.
1331: if (!isServiceEnabled()) {
1332: return false;
1333: }
1334: if (name == null && node == null) {
1335: // We always have info about the MUC service
1336: return true;
1337: } else if (name != null && node == null) {
1338: // We only have info if the room exists
1339: return hasChatRoom(name);
1340: } else if (name != null && "x-roomuser-item".equals(node)) {
1341: // We always have info about reserved names as long as the room exists
1342: return hasChatRoom(name);
1343: }
1344: return false;
1345: }
1346:
1347: public Iterator<DiscoItem> getItems(String name, String node,
1348: JID senderJID) {
1349: // Check if the service is disabled. Info is not available when disabled.
1350: if (!isServiceEnabled()) {
1351: return null;
1352: }
1353: List<DiscoItem> answer = new ArrayList<DiscoItem>();
1354: if (name == null && node == null) {
1355: // Answer all the public rooms as items
1356: for (MUCRoom room : rooms.values()) {
1357: if (canDiscoverRoom(room)) {
1358: answer.add(new DiscoItem(room.getRole()
1359: .getRoleAddress(), room
1360: .getNaturalLanguageName(), null, null));
1361: }
1362: }
1363: } else if (name != null && node == null) {
1364: // Answer the room occupants as items if that info is publicly available
1365: MUCRoom room = getChatRoom(name);
1366: if (room != null && canDiscoverRoom(room)) {
1367: for (MUCRole role : room.getOccupants()) {
1368: // TODO Should we filter occupants that are invisible (presence is not broadcasted)?
1369: answer.add(new DiscoItem(role.getRoleAddress(),
1370: null, null, null));
1371: }
1372: }
1373: }
1374: return answer.iterator();
1375: }
1376:
1377: private boolean canDiscoverRoom(MUCRoom room) {
1378: // Check if locked rooms may be discovered
1379: if (!allowToDiscoverLockedRooms && room.isLocked()) {
1380: return false;
1381: }
1382: return room.isPublicRoom();
1383: }
1384:
1385: public void addListener(MUCEventListener listener) {
1386: listeners.add(listener);
1387: }
1388:
1389: public void removeListener(MUCEventListener listener) {
1390: listeners.remove(listener);
1391: }
1392:
1393: void fireOccupantJoined(JID roomJID, JID user, String nickname) {
1394: for (MUCEventListener listener : listeners) {
1395: listener.occupantJoined(roomJID, user, nickname);
1396: }
1397: }
1398:
1399: void fireOccupantLeft(JID roomJID, JID user) {
1400: for (MUCEventListener listener : listeners) {
1401: listener.occupantLeft(roomJID, user);
1402: }
1403: }
1404:
1405: void fireNicknameChanged(JID roomJID, JID user, String oldNickname,
1406: String newNickname) {
1407: for (MUCEventListener listener : listeners) {
1408: listener.nicknameChanged(roomJID, user, oldNickname,
1409: newNickname);
1410: }
1411: }
1412:
1413: void fireMessageReceived(JID roomJID, JID user, String nickname,
1414: Message message) {
1415: for (MUCEventListener listener : listeners) {
1416: listener.messageReceived(roomJID, user, nickname, message);
1417: }
1418: }
1419:
1420: void fireRoomDestroyed(JID roomJID) {
1421: for (MUCEventListener listener : listeners) {
1422: listener.roomDestroyed(roomJID);
1423: }
1424: }
1425:
1426: /**
1427: * Converts an array to a comma-delimitted String.
1428: *
1429: * @param array the array.
1430: * @return a comma delimtted String of the array values.
1431: */
1432: private static String fromArray(String[] array) {
1433: StringBuilder buf = new StringBuilder();
1434: for (int i = 0; i < array.length; i++) {
1435: buf.append(array[i]);
1436: if (i != array.length - 1) {
1437: buf.append(",");
1438: }
1439: }
1440: return buf.toString();
1441: }
1442:
1443: /****************** Statistics code ************************/
1444: private void addTotalRoomStats() {
1445: // Register a statistic.
1446: Statistic statistic = new Statistic() {
1447: public String getName() {
1448: return LocaleUtils
1449: .getLocalizedString("muc.stats.active_group_chats.name");
1450: }
1451:
1452: public Type getStatType() {
1453: return Type.count;
1454: }
1455:
1456: public String getDescription() {
1457: return LocaleUtils
1458: .getLocalizedString("muc.stats.active_group_chats.desc");
1459: }
1460:
1461: public String getUnits() {
1462: return LocaleUtils
1463: .getLocalizedString("muc.stats.active_group_chats.units");
1464: }
1465:
1466: public double sample() {
1467: return getNumberChatRooms();
1468: }
1469:
1470: public boolean isPartialSample() {
1471: return false;
1472: }
1473: };
1474: StatisticsManager.getInstance().addStatistic(roomsStatKey,
1475: statistic);
1476: }
1477:
1478: private void addTotalOccupantsStats() {
1479: // Register a statistic.
1480: Statistic statistic = new Statistic() {
1481: public String getName() {
1482: return LocaleUtils
1483: .getLocalizedString("muc.stats.occupants.name");
1484: }
1485:
1486: public Type getStatType() {
1487: return Type.count;
1488: }
1489:
1490: public String getDescription() {
1491: return LocaleUtils
1492: .getLocalizedString("muc.stats.occupants.description");
1493: }
1494:
1495: public String getUnits() {
1496: return LocaleUtils
1497: .getLocalizedString("muc.stats.occupants.label");
1498: }
1499:
1500: public double sample() {
1501: return getNumberRoomOccupants();
1502: }
1503:
1504: public boolean isPartialSample() {
1505: return false;
1506: }
1507: };
1508: StatisticsManager.getInstance().addStatistic(occupantsStatKey,
1509: statistic);
1510: }
1511:
1512: private void addTotalConnectedUsers() {
1513: // Register a statistic.
1514: Statistic statistic = new Statistic() {
1515: public String getName() {
1516: return LocaleUtils
1517: .getLocalizedString("muc.stats.users.name");
1518: }
1519:
1520: public Type getStatType() {
1521: return Type.count;
1522: }
1523:
1524: public String getDescription() {
1525: return LocaleUtils
1526: .getLocalizedString("muc.stats.users.description");
1527: }
1528:
1529: public String getUnits() {
1530: return LocaleUtils
1531: .getLocalizedString("muc.stats.users.label");
1532: }
1533:
1534: public double sample() {
1535: return getNumberConnectedUsers(false);
1536: }
1537:
1538: public boolean isPartialSample() {
1539: return false;
1540: }
1541: };
1542: StatisticsManager.getInstance().addStatistic(usersStatKey,
1543: statistic);
1544: }
1545:
1546: private void addNumberIncomingMessages() {
1547: // Register a statistic.
1548: Statistic statistic = new Statistic() {
1549: public String getName() {
1550: return LocaleUtils
1551: .getLocalizedString("muc.stats.incoming.name");
1552: }
1553:
1554: public Type getStatType() {
1555: return Type.rate;
1556: }
1557:
1558: public String getDescription() {
1559: return LocaleUtils
1560: .getLocalizedString("muc.stats.incoming.description");
1561: }
1562:
1563: public String getUnits() {
1564: return LocaleUtils
1565: .getLocalizedString("muc.stats.incoming.label");
1566: }
1567:
1568: public double sample() {
1569: return inMessages.getAndSet(0);
1570: }
1571:
1572: public boolean isPartialSample() {
1573: // Get this value from the other cluster nodes
1574: return true;
1575: }
1576: };
1577: StatisticsManager.getInstance().addMultiStatistic(
1578: incomingStatKey, trafficStatGroup, statistic);
1579: }
1580:
1581: private void addNumberOutgoingMessages() {
1582: // Register a statistic.
1583: Statistic statistic = new Statistic() {
1584: public String getName() {
1585: return LocaleUtils
1586: .getLocalizedString("muc.stats.outgoing.name");
1587: }
1588:
1589: public Type getStatType() {
1590: return Type.rate;
1591: }
1592:
1593: public String getDescription() {
1594: return LocaleUtils
1595: .getLocalizedString("muc.stats.outgoing.description");
1596: }
1597:
1598: public String getUnits() {
1599: return LocaleUtils
1600: .getLocalizedString("muc.stats.outgoing.label");
1601: }
1602:
1603: public double sample() {
1604: return outMessages.getAndSet(0);
1605: }
1606:
1607: public boolean isPartialSample() {
1608: // Each cluster node knows the total across the cluster
1609: return false;
1610: }
1611: };
1612: StatisticsManager.getInstance().addMultiStatistic(
1613: outgoingStatKey, trafficStatGroup, statistic);
1614: }
1615: }
|