KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > messenger > muc > spi > MultiUserChatServerImpl


1 /**
2  * $RCSfile: MultiUserChatServerImpl.java,v $
3  * $Revision: 1.41 $
4  * $Date: 2005/07/26 05:09:55 $
5  *
6  * Copyright (C) 2004 Jive Software. All rights reserved.
7  *
8  * This software is published under the terms of the GNU Public License (GPL),
9  * a copy of which is included in this distribution.
10  */

11
12 package org.jivesoftware.messenger.muc.spi;
13
14 import java.util.*;
15 import java.util.concurrent.ConcurrentHashMap JavaDoc;
16 import java.util.concurrent.CopyOnWriteArrayList JavaDoc;
17 import java.util.concurrent.LinkedBlockingQueue JavaDoc;
18 import java.text.DateFormat JavaDoc;
19 import java.text.SimpleDateFormat JavaDoc;
20
21 import org.dom4j.DocumentHelper;
22 import org.dom4j.Element;
23 import org.jivesoftware.messenger.*;
24 import org.jivesoftware.messenger.auth.UnauthorizedException;
25 import org.jivesoftware.messenger.container.BasicModule;
26 import org.jivesoftware.messenger.disco.DiscoInfoProvider;
27 import org.jivesoftware.messenger.disco.DiscoItemsProvider;
28 import org.jivesoftware.messenger.disco.DiscoServerItem;
29 import org.jivesoftware.messenger.disco.ServerItemsProvider;
30 import org.jivesoftware.messenger.forms.DataForm;
31 import org.jivesoftware.messenger.forms.FormField;
32 import org.jivesoftware.messenger.forms.spi.XDataFormImpl;
33 import org.jivesoftware.messenger.forms.spi.XFormFieldImpl;
34 import org.jivesoftware.messenger.muc.HistoryStrategy;
35 import org.jivesoftware.messenger.muc.MUCRole;
36 import org.jivesoftware.messenger.muc.MUCRoom;
37 import org.jivesoftware.messenger.muc.MUCUser;
38 import org.jivesoftware.messenger.muc.MultiUserChatServer;
39 import org.jivesoftware.messenger.muc.NotAllowedException;
40 import org.jivesoftware.messenger.user.UserNotFoundException;
41 import org.jivesoftware.util.LocaleUtils;
42 import org.jivesoftware.util.Log;
43 import org.jivesoftware.util.JiveGlobals;
44 import org.xmpp.packet.*;
45 import org.xmpp.component.ComponentManager;
46
47 /**
48  * Implements the chat server as a cached memory resident chat server. The server is also
49  * responsible for responding Multi-User Chat disco requests as well as removing inactive users from
50  * the rooms after a period of time and to maintain a log of the conversation in the rooms that
51  * require to log their conversations. The conversations log is saved to the database using a
52  * separate process<p>
53  *
54  * Temporary rooms are held in memory as long as they have occupants. They will be destroyed after
55  * the last occupant left the room. On the other hand, persistent rooms are always present in memory
56  * even after the last occupant left the room. In order to keep memory clean of peristent rooms that
57  * have been forgotten or abandonded this class includes a clean up process. The clean up process
58  * will remove from memory rooms that haven't had occupants for a while. Moreover, forgotten or
59  * abandoned rooms won't be loaded into memory when the Multi-User Chat service starts up.
60  *
61  * @author Gaston Dombiak
62  */

63 public class MultiUserChatServerImpl extends BasicModule implements MultiUserChatServer,
64         ServerItemsProvider, DiscoInfoProvider, DiscoItemsProvider, RoutableChannelHandler {
65
66     private static final DateFormat JavaDoc dateFormatter = new SimpleDateFormat JavaDoc("yyyyMMdd'T'HH:mm:ss");
67     static {
68         dateFormatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
69     }
70     /**
71      * The time to elapse between clearing of idle chat users.
72      */

73     private int user_timeout = 300000;
74     /**
75      * The number of milliseconds a user must be idle before he/she gets kicked from all the rooms.
76      */

77     private int user_idle = -1;
78     /**
79      * Task that kicks idle users from the rooms.
80      */

81     private UserTimeoutTask userTimeoutTask;
82     /**
83      * The time to elapse between logging the room conversations.
84      */

85     private int log_timeout = 300000;
86     /**
87      * The number of messages to log on each run of the logging process.
88      */

89     private int log_batch_size = 50;
90     /**
91      * Task that flushes room conversation logs to the database.
92      */

93     private LogConversationTask logConversationTask;
94     /**
95      * the chat service's hostname
96      */

97     private String JavaDoc chatServiceName = null;
98
99     /**
100      * chatrooms managed by this manager, table: key room name (String); value ChatRoom
101      */

102     private Map JavaDoc<String JavaDoc,MUCRoom> rooms = new ConcurrentHashMap JavaDoc<String JavaDoc,MUCRoom>();
103
104     /**
105      * chat users managed by this manager, table: key user jid (XMPPAddress); value ChatUser
106      */

107     private Map JavaDoc<JID, MUCUser> users = new ConcurrentHashMap JavaDoc<JID, MUCUser>();
108     private HistoryStrategy historyStrategy;
109
110     private RoutingTable routingTable = null;
111     /**
112      * The packet router for the server.
113      */

114     private PacketRouter router = null;
115     /**
116      * The handler of packets with namespace jabber:iq:register for the server.
117      */

118     private IQMUCRegisterHandler registerHandler = null;
119     /**
120      * The total time all agents took to chat *
121      */

122     public long totalChatTime;
123
124     /**
125      * Timer to monitor chatroom participants. If they've been idle for too long, probe for
126      * presence.
127      */

128     private Timer timer = new Timer("MUC cleanup");
129
130     /**
131      * Flag that indicates if the service should provide information about locked rooms when
132      * handling service discovery requests.
133      * Note: Setting this flag in false is not compliant with the spec. A user may try to join a
134      * locked room thinking that the room doesn't exist because the user didn't discover it before.
135      */

136     private boolean allowToDiscoverLockedRooms = true;
137
138     /**
139      * Returns the permission policy for creating rooms. A true value means that not anyone can
140      * create a room, only the JIDs listed in <code>allowedToCreate</code> are allowed to create
141      * rooms.
142      */

143     private boolean roomCreationRestricted = false;
144
145     /**
146      * Bare jids of users that are allowed to create MUC rooms. An empty list means that anyone can
147      * create a room.
148      */

149     private Collection<String JavaDoc> allowedToCreate = new CopyOnWriteArrayList JavaDoc<String JavaDoc>();
150
151     /**
152      * Bare jids of users that are system administrators of the MUC service. A sysadmin has the same
153      * permissions as a room owner.
154      */

155     private Collection<String JavaDoc> sysadmins = new CopyOnWriteArrayList JavaDoc<String JavaDoc>();
156
157     /**
158      * Queue that holds the messages to log for the rooms that need to log their conversations.
159      */

160     private Queue JavaDoc<ConversationLogEntry> logQueue = new LinkedBlockingQueue JavaDoc<ConversationLogEntry>();
161
162     /**
163      * Max number of hours that a persistent room may be empty before the service removes the
164      * room from memory. Unloaded rooms will exist in the database and may be loaded by a user
165      * request. Default time limit is: 7 days.
166      */

167     private long emptyLimit = 7 * 24;
168     /**
169      * Task that removes rooms from memory that have been without activity for a period of time. A
170      * room is considered without activity when no occupants are present in the room for a while.
171      */

172     private CleanupTask cleanupTask;
173     /**
174      * The time to elapse between each rooms cleanup. Default frequency is 60 minutes.
175      */

176     private final long cleanup_frequency = 60 * 60 * 1000;
177
178     /**
179      * Create a new group chat server.
180      */

181     public MultiUserChatServerImpl() {
182         super("Basic multi user chat server");
183         historyStrategy = new HistoryStrategy(null);
184     }
185
186     public String JavaDoc getDescription() {
187         return null;
188     }
189
190     public void process(Packet packet) throws UnauthorizedException, PacketException {
191         // TODO Remove this method when moving MUC as a component and removing module code
192
processPacket(packet);
193     }
194
195     public void processPacket(Packet packet) {
196         // The MUC service will receive all the packets whose domain matches the domain of the MUC
197
// service. This means that, for instance, a disco request should be responded by the
198
// service itself instead of relying on the server to handle the request.
199
try {
200             // Check if the packet is a disco request or a packet with namespace iq:register
201
if (packet instanceof IQ) {
202                 if (process((IQ)packet)) {
203                     return;
204                 }
205             }
206             // The packet is a normal packet that should possibly be sent to the room
207
MUCUser user = getChatUser(packet.getFrom());
208             user.process(packet);
209         }
210         catch (Exception JavaDoc e) {
211             Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
212         }
213     }
214
215     /**
216      * Returns true if the IQ packet was processed. This method should only process disco packets
217      * as well as jabber:iq:register packets sent to the MUC service.
218      *
219      * @param iq the IQ packet to process.
220      * @return true if the IQ packet was processed.
221      */

222     private boolean process(IQ iq) {
223         Element childElement = iq.getChildElement();
224         String JavaDoc namespace = null;
225         if (childElement != null) {
226             namespace = childElement.getNamespaceURI();
227         }
228         if ("jabber:iq:register".equals(namespace)) {
229             IQ reply = registerHandler.handleIQ(iq);
230             router.route(reply);
231         }
232         else if ("http://jabber.org/protocol/disco#info".equals(namespace)) {
233             try {
234                 // TODO MUC should have an IQDiscoInfoHandler of its own when MUC becomes
235
// a component
236
IQ reply = XMPPServer.getInstance().getIQDiscoInfoHandler().handleIQ(iq);
237                 router.route(reply);
238             }
239             catch (UnauthorizedException e) {
240                 // Do nothing. This error should never happen
241
}
242         }
243         else if ("http://jabber.org/protocol/disco#items".equals(namespace)) {
244             try {
245                 // TODO MUC should have an IQDiscoItemsHandler of its own when MUC becomes
246
// a component
247
IQ reply = XMPPServer.getInstance().getIQDiscoItemsHandler().handleIQ(iq);
248                 router.route(reply);
249             }
250             catch (UnauthorizedException e) {
251                 // Do nothing. This error should never happen
252
}
253         }
254         else {
255             return false;
256         }
257         return true;
258     }
259
260     public void initialize(JID jid, ComponentManager componentManager) {
261
262     }
263
264     public void shutdown() {
265
266     }
267
268     public String JavaDoc getServiceDomain() {
269         return chatServiceName + "." + XMPPServer.getInstance().getServerInfo().getName();
270     }
271
272     public JID getAddress() {
273         return new JID(null, getServiceDomain(), null);
274     }
275
276     /**
277      * Probes the presence of any user who's last packet was sent more than 5 minute ago.
278      */

279     private class UserTimeoutTask extends TimerTask {
280         /**
281          * Remove any user that has been idle for longer than the user timeout time.
282          */

283         public void run() {
284             checkForTimedOutUsers();
285         }
286     }
287
288     private void checkForTimedOutUsers() {
289         // Do nothing if this feature is disabled (i.e USER_IDLE equals -1)
290
if (user_idle == -1) {
291             return;
292         }
293         final long deadline = System.currentTimeMillis() - user_idle;
294         for (MUCUser user : users.values()) {
295             try {
296                 if (user.getLastPacketTime() < deadline) {
297                     // Kick the user from all the rooms that he/she had previuosly joined
298
Iterator<MUCRole> roles = user.getRoles();
299                     MUCRole role;
300                     MUCRoom room;
301                     Presence kickedPresence;
302                     while (roles.hasNext()) {
303                         role = roles.next();
304                         room = role.getChatRoom();
305                         try {
306                             kickedPresence =
307                                     room.kickOccupant(user.getAddress(), null, null);
308                             // Send the updated presence to the room occupants
309
room.send(kickedPresence);
310                         }
311                         catch (NotAllowedException e) {
312                             // Do nothing since we cannot kick owners or admins
313
}
314                     }
315                 }
316             }
317             catch (Throwable JavaDoc e) {
318                 Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
319             }
320         }
321     }
322
323     /**
324      * Logs the conversation of the rooms that have this feature enabled.
325      */

326     private class LogConversationTask extends TimerTask {
327         public void run() {
328             try {
329                 logConversation();
330             }
331             catch (Throwable JavaDoc e) {
332                 Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
333             }
334         }
335     }
336
337     private void logConversation() {
338         ConversationLogEntry entry = null;
339         boolean success = false;
340         for (int index = 0; index <= log_batch_size && !logQueue.isEmpty(); index++) {
341             entry = logQueue.poll();
342             if (entry != null) {
343                 success = MUCPersistenceManager.saveConversationLogEntry(entry);
344                 if (!success) {
345                     logQueue.add(entry);
346                 }
347             }
348         }
349     }
350
351     /**
352      * Logs all the remaining conversation log entries to the database. Use this method to force
353      * saving all the conversation log entries before the service becomes unavailable.
354      */

355     private void logAllConversation() {
356         ConversationLogEntry entry = null;
357         while (!logQueue.isEmpty()) {
358             entry = logQueue.poll();
359             if (entry != null) {
360                 MUCPersistenceManager.saveConversationLogEntry(entry);
361             }
362         }
363     }
364
365     /**
366      * Removes from memory rooms that have been without activity for a period of time. A room is
367      * considered without activity when no occupants are present in the room for a while.
368      */

369     private class CleanupTask extends TimerTask {
370         public void run() {
371             try {
372                 cleanupRooms();
373             }
374             catch (Throwable JavaDoc e) {
375                 Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
376             }
377         }
378     }
379
380     private void cleanupRooms() {
381         for (MUCRoom room : rooms.values()) {
382             if (room.getEmptyDate() != null && room.getEmptyDate().before(getCleanupDate())) {
383                 removeChatRoom(room.getName());
384             }
385         }
386     }
387
388     public MUCRoom getChatRoom(String JavaDoc roomName, JID userjid) throws UnauthorizedException {
389         MUCRoom room = null;
390         synchronized (roomName.intern()) {
391             room = rooms.get(roomName.toLowerCase());
392             if (room == null) {
393                 room = new MUCRoomImpl(this, roomName, router);
394                 // If the room is persistent load the configuration values from the DB
395
try {
396                     // Try to load the room's configuration from the database (if the room is
397
// persistent but was added to the DB after the server was started up or the
398
// room may be an old room that was not present in memory)
399
MUCPersistenceManager.loadFromDB((MUCRoomImpl) room);
400                 }
401                 catch (IllegalArgumentException JavaDoc e) {
402                     // The room does not exist so check for creation permissions
403
// Room creation is always allowed for sysadmin
404
if (isRoomCreationRestricted() &&
405                             !sysadmins.contains(userjid.toBareJID())) {
406                         // The room creation is only allowed for certain JIDs
407
if (!allowedToCreate.contains(userjid.toBareJID())) {
408                             // The user is not in the list of allowed JIDs to create a room so raise
409
// an exception
410
throw new UnauthorizedException();
411                         }
412                     }
413                     room.addFirstOwner(userjid.toBareJID());
414                 }
415                 rooms.put(roomName.toLowerCase(), room);
416             }
417         }
418         return room;
419     }
420
421     public MUCRoom getChatRoom(String JavaDoc roomName) {
422         MUCRoom room = rooms.get(roomName.toLowerCase());
423         if (room == null) {
424             // Check if the room exists in the database and was not present in memory
425
synchronized (roomName.intern()) {
426                 room = rooms.get(roomName.toLowerCase());
427                 if (room == null) {
428                     room = new MUCRoomImpl(this, roomName, router);
429                     // If the room is persistent load the configuration values from the DB
430
try {
431                         // Try to load the room's configuration from the database (if the room is
432
// persistent but was added to the DB after the server was started up or the
433
// room may be an old room that was not present in memory)
434
MUCPersistenceManager.loadFromDB((MUCRoomImpl) room);
435                         rooms.put(roomName.toLowerCase(), room);
436                     }
437                     catch (IllegalArgumentException JavaDoc e) {
438                         // The room does not exist so do nothing
439
room = null;
440                     }
441                 }
442             }
443         }
444         return room;
445     }
446
447     public List JavaDoc<MUCRoom> getChatRooms() {
448         return new ArrayList JavaDoc<MUCRoom>(rooms.values());
449     }
450
451     public boolean hasChatRoom(String JavaDoc roomName) {
452         return getChatRoom(roomName) != null;
453     }
454
455     public void removeChatRoom(String JavaDoc roomName) {
456         final MUCRoom room = rooms.remove(roomName.toLowerCase());
457         if (room != null) {
458             final long chatLength = room.getChatLength();
459             totalChatTime += chatLength;
460         }
461     }
462
463     public String JavaDoc getServiceName() {
464         return chatServiceName;
465     }
466
467     public HistoryStrategy getHistoryStrategy() {
468         return historyStrategy;
469     }
470
471     public void removeUser(JID jabberID) {
472         MUCUser user = users.remove(jabberID);
473         if (user != null) {
474             Iterator<MUCRole> roles = user.getRoles();
475             while (roles.hasNext()) {
476                 MUCRole role = roles.next();
477                 try {
478                     role.getChatRoom().leaveRoom(role.getNickname());
479                 }
480                 catch (Exception JavaDoc e) {
481                     Log.error(e);
482                 }
483             }
484         }
485     }
486
487     public MUCUser getChatUser(JID userjid) throws UserNotFoundException {
488         if (router == null) {
489             throw new IllegalStateException JavaDoc("Not initialized");
490         }
491         MUCUser user = null;
492         synchronized (userjid.toString().intern()) {
493             user = users.get(userjid);
494             if (user == null) {
495                 user = new MUCUserImpl(this, router, userjid);
496                 users.put(userjid, user);
497             }
498         }
499         return user;
500     }
501
502     public void serverBroadcast(String JavaDoc msg) throws UnauthorizedException {
503         for (MUCRoom room : rooms.values()) {
504             room.serverBroadcast(msg);
505         }
506     }
507
508     public void setServiceName(String JavaDoc name) {
509         JiveGlobals.setProperty("xmpp.muc.service", name);
510     }
511
512     /**
513      * Returns the limit date after which rooms whithout activity will be removed from memory.
514      *
515      * @return the limit date after which rooms whithout activity will be removed from memory.
516      */

517     private Date getCleanupDate() {
518         return new Date(System.currentTimeMillis() - (emptyLimit * 3600000));
519     }
520
521     public void setKickIdleUsersTimeout(int timeout) {
522         if (this.user_timeout == timeout) {
523             return;
524         }
525         // Cancel the existing task because the timeout has changed
526
if (userTimeoutTask != null) {
527             userTimeoutTask.cancel();
528         }
529         this.user_timeout = timeout;
530         // Create a new task and schedule it with the new timeout
531
userTimeoutTask = new UserTimeoutTask();
532         timer.schedule(userTimeoutTask, user_timeout, user_timeout);
533         // Set the new property value
534
JiveGlobals.setProperty("xmpp.muc.tasks.user.timeout", Integer.toString(timeout));
535     }
536
537     public int getKickIdleUsersTimeout() {
538         return user_timeout;
539     }
540
541     public void setUserIdleTime(int idleTime) {
542         if (this.user_idle == idleTime) {
543             return;
544         }
545         this.user_idle = idleTime;
546         // Set the new property value
547
JiveGlobals.setProperty("xmpp.muc.tasks.user.idle", Integer.toString(idleTime));
548     }
549
550     public int getUserIdleTime() {
551         return user_idle;
552     }
553
554     public void setLogConversationsTimeout(int timeout) {
555         if (this.log_timeout == timeout) {
556             return;
557         }
558         // Cancel the existing task because the timeout has changed
559
if (logConversationTask != null) {
560             logConversationTask.cancel();
561         }
562         this.log_timeout = timeout;
563         // Create a new task and schedule it with the new timeout
564
logConversationTask = new LogConversationTask();
565         timer.schedule(logConversationTask, log_timeout, log_timeout);
566         // Set the new property value
567
JiveGlobals.setProperty("xmpp.muc.tasks.log.timeout", Integer.toString(timeout));
568     }
569
570     public int getLogConversationsTimeout() {
571         return log_timeout;
572     }
573
574     public void setLogConversationBatchSize(int size) {
575         if (this.log_batch_size == size) {
576             return;
577         }
578         this.log_batch_size = size;
579         // Set the new property value
580
JiveGlobals.setProperty("xmpp.muc.tasks.log.batchsize", Integer.toString(size));
581     }
582
583     public int getLogConversationBatchSize() {
584         return log_batch_size;
585     }
586
587     public Collection<String JavaDoc> getUsersAllowedToCreate() {
588         return allowedToCreate;
589     }
590
591     public Collection<String JavaDoc> getSysadmins() {
592         return sysadmins;
593     }
594
595     public void addSysadmin(String JavaDoc userJID) {
596         sysadmins.add(userJID.trim().toLowerCase());
597         // Update the config.
598
String JavaDoc[] jids = new String JavaDoc[sysadmins.size()];
599         jids = (String JavaDoc[])sysadmins.toArray(jids);
600         JiveGlobals.setProperty("xmpp.muc.sysadmin.jid", fromArray(jids));
601     }
602
603     public void removeSysadmin(String JavaDoc userJID) {
604         sysadmins.remove(userJID.trim().toLowerCase());
605         // Update the config.
606
String JavaDoc[] jids = new String JavaDoc[sysadmins.size()];
607         jids = (String JavaDoc[])sysadmins.toArray(jids);
608         JiveGlobals.setProperty("xmpp.muc.sysadmin.jid", fromArray(jids));
609     }
610
611     /**
612      * Returns the flag that indicates if the service should provide information about locked rooms
613      * when handling service discovery requests.
614      *
615      * @return true if the service should provide information about locked rooms.
616      */

617     public boolean isAllowToDiscoverLockedRooms() {
618         return allowToDiscoverLockedRooms;
619     }
620
621     /**
622      * Sets the flag that indicates if the service should provide information about locked rooms
623      * when handling service discovery requests.
624      * Note: Setting this flag in false is not compliant with the spec. A user may try to join a
625      * locked room thinking that the room doesn't exist because the user didn't discover it before.
626      *
627      * @param allowToDiscoverLockedRooms if the service should provide information about locked
628      * rooms.
629      */

630     public void setAllowToDiscoverLockedRooms(boolean allowToDiscoverLockedRooms) {
631         this.allowToDiscoverLockedRooms = allowToDiscoverLockedRooms;
632         JiveGlobals.setProperty("xmpp.muc.discover.locked",
633                 Boolean.toString(allowToDiscoverLockedRooms));
634     }
635
636     public boolean isRoomCreationRestricted() {
637         return roomCreationRestricted;
638     }
639
640     public void setRoomCreationRestricted(boolean roomCreationRestricted) {
641         this.roomCreationRestricted = roomCreationRestricted;
642         JiveGlobals.setProperty("xmpp.muc.create.anyone", Boolean.toString(roomCreationRestricted));
643     }
644
645     public void addUserAllowedToCreate(String JavaDoc userJID) {
646         // Update the list of allowed JIDs to create MUC rooms. Since we are updating the instance
647
// variable there is no need to restart the service
648
allowedToCreate.add(userJID.trim().toLowerCase());
649         // Update the config.
650
String JavaDoc[] jids = new String JavaDoc[allowedToCreate.size()];
651         jids = (String JavaDoc[])allowedToCreate.toArray(jids);
652         JiveGlobals.setProperty("xmpp.muc.create.jid", fromArray(jids));
653     }
654
655     public void removeUserAllowedToCreate(String JavaDoc userJID) {
656         // Update the list of allowed JIDs to create MUC rooms. Since we are updating the instance
657
// variable there is no need to restart the service
658
allowedToCreate.remove(userJID.trim().toLowerCase());
659         // Update the config.
660
String JavaDoc[] jids = new String JavaDoc[allowedToCreate.size()];
661         jids = (String JavaDoc[])allowedToCreate.toArray(jids);
662         JiveGlobals.setProperty("xmpp.muc.create.jid", fromArray(jids));
663     }
664
665     public void initialize(XMPPServer server) {
666         super.initialize(server);
667
668         chatServiceName = JiveGlobals.getProperty("xmpp.muc.service");
669         // Trigger the strategy to load itself from the context
670
historyStrategy.setContext("xmpp.muc.history");
671         // Load the list of JIDs that are sysadmins of the MUC service
672
String JavaDoc property = JiveGlobals.getProperty("xmpp.muc.sysadmin.jid");
673         String JavaDoc[] jids;
674         if (property != null) {
675             jids = property.split(",");
676             for (int i = 0; i < jids.length; i++) {
677                 sysadmins.add(jids[i].trim().toLowerCase());
678             }
679         }
680         allowToDiscoverLockedRooms =
681                 Boolean.parseBoolean(JiveGlobals.getProperty("xmpp.muc.discover.locked", "true"));
682         roomCreationRestricted =
683                 Boolean.parseBoolean(JiveGlobals.getProperty("xmpp.muc.create.anyone", "false"));
684         // Load the list of JIDs that are allowed to create a MUC room
685
property = JiveGlobals.getProperty("xmpp.muc.create.jid");
686         if (property != null) {
687             jids = property.split(",");
688             for (int i = 0; i < jids.length; i++) {
689                 allowedToCreate.add(jids[i].trim().toLowerCase());
690             }
691         }
692         String JavaDoc value = JiveGlobals.getProperty("xmpp.muc.tasks.user.timeout");
693         if (value != null) {
694             try {
695                 user_timeout = Integer.parseInt(value);
696             }
697             catch (NumberFormatException JavaDoc e) {
698                 Log.error("Wrong number format of property xmpp.muc.tasks.user.timeout", e);
699             }
700         }
701         value = JiveGlobals.getProperty("xmpp.muc.tasks.user.idle");
702         if (value != null) {
703             try {
704                 user_idle = Integer.parseInt(value);
705             }
706             catch (NumberFormatException JavaDoc e) {
707                 Log.error("Wrong number format of property xmpp.muc.tasks.user.idle", e);
708             }
709         }
710         value = JiveGlobals.getProperty("xmpp.muc.tasks.log.timeout");
711         if (value != null) {
712             try {
713                 log_timeout = Integer.parseInt(value);
714             }
715             catch (NumberFormatException JavaDoc e) {
716                 Log.error("Wrong number format of property xmpp.muc.tasks.log.timeout", e);
717             }
718         }
719         value = JiveGlobals.getProperty("xmpp.muc.tasks.log.batchsize");
720         if (value != null) {
721             try {
722                 log_batch_size = Integer.parseInt(value);
723             }
724             catch (NumberFormatException JavaDoc e) {
725                 Log.error("Wrong number format of property xmpp.muc.tasks.log.batchsize", e);
726             }
727         }
728         if (chatServiceName == null) {
729             chatServiceName = "conference";
730         }
731         // Run through the users every 5 minutes after a 5 minutes server startup delay (default
732
// values)
733
userTimeoutTask = new UserTimeoutTask();
734         timer.schedule(userTimeoutTask, user_timeout, user_timeout);
735         // Log the room conversations every 5 minutes after a 5 minutes server startup delay
736
// (default values)
737
logConversationTask = new LogConversationTask();
738         timer.schedule(logConversationTask, log_timeout, log_timeout);
739         // Remove unused rooms from memory
740
cleanupTask = new CleanupTask();
741         timer.schedule(cleanupTask, cleanup_frequency, cleanup_frequency);
742
743         routingTable = server.getRoutingTable();
744         router = server.getPacketRouter();
745         // Configure the handler of iq:register packets
746
registerHandler = new IQMUCRegisterHandler(this);
747     }
748
749     public void start() {
750         super.start();
751         // Add the route to this service
752
routingTable.addRoute(getAddress(), this);
753         ArrayList JavaDoc params = new ArrayList JavaDoc();
754         params.clear();
755         params.add(getServiceDomain());
756         Log.info(LocaleUtils.getLocalizedString("startup.starting.muc", params));
757         // Load all the persistent rooms to memory
758
for (MUCRoom room : MUCPersistenceManager.loadRoomsFromDB(this, this.getCleanupDate(),
759                 router)) {
760             rooms.put(room.getName().toLowerCase(), room);
761         }
762     }
763
764     public void stop() {
765         super.stop();
766         // Remove the route to this service
767
routingTable.removeRoute(getAddress());
768         timer.cancel();
769         logAllConversation();
770     }
771
772     public long getTotalChatTime() {
773         return totalChatTime;
774     }
775
776     public void logConversation(MUCRoom room, Message message, JID sender) {
777         logQueue.add(new ConversationLogEntry(new Date(), room, message, sender));
778     }
779
780     public Iterator getItems() {
781         ArrayList JavaDoc items = new ArrayList JavaDoc();
782
783         items.add(new DiscoServerItem() {
784             public String JavaDoc getJID() {
785                 return getServiceDomain();
786             }
787
788             public String JavaDoc getName() {
789                 return "Public Chatrooms";
790             }
791
792             public String JavaDoc getAction() {
793                 return null;
794             }
795
796             public String JavaDoc getNode() {
797                 return null;
798             }
799
800             public DiscoInfoProvider getDiscoInfoProvider() {
801                 return MultiUserChatServerImpl.this;
802             }
803
804             public DiscoItemsProvider getDiscoItemsProvider() {
805                 return MultiUserChatServerImpl.this;
806             }
807         });
808         return items.iterator();
809     }
810
811     public Iterator<Element> getIdentities(String JavaDoc name, String JavaDoc node, JID senderJID) {
812         ArrayList JavaDoc<Element> identities = new ArrayList JavaDoc<Element>();
813         if (name == null && node == null) {
814             // Answer the identity of the MUC service
815
Element identity = DocumentHelper.createElement("identity");
816             identity.addAttribute("category", "conference");
817             identity.addAttribute("name", "Public Chatrooms");
818             identity.addAttribute("type", "text");
819
820             identities.add(identity);
821         }
822         else if (name != null && node == null) {
823             // Answer the identity of a given room
824
MUCRoom room = getChatRoom(name);
825             if (room != null && canDiscoverRoom(room)) {
826                 Element identity = DocumentHelper.createElement("identity");
827                 identity.addAttribute("category", "conference");
828                 identity.addAttribute("name", room.getNaturalLanguageName());
829                 identity.addAttribute("type", "text");
830
831                 identities.add(identity);
832             }
833         }
834         else if (name != null && "x-roomuser-item".equals(node)) {
835             // Answer reserved nickname for the sender of the disco request in the requested room
836
MUCRoom room = getChatRoom(name);
837             if (room != null) {
838                 String JavaDoc reservedNick = room.getReservedNickname(senderJID.toBareJID());
839                 if (reservedNick != null) {
840                     Element identity = DocumentHelper.createElement("identity");
841                     identity.addAttribute("category", "conference");
842                     identity.addAttribute("name", reservedNick);
843                     identity.addAttribute("type", "text");
844
845                     identities.add(identity);
846                 }
847             }
848         }
849         return identities.iterator();
850     }
851
852     public Iterator<String JavaDoc> getFeatures(String JavaDoc name, String JavaDoc node, JID senderJID) {
853         ArrayList JavaDoc<String JavaDoc> features = new ArrayList JavaDoc<String JavaDoc>();
854         if (name == null && node == null) {
855             // Answer the features of the MUC service
856
features.add("http://jabber.org/protocol/muc");
857             features.add("http://jabber.org/protocol/disco#info");
858             features.add("http://jabber.org/protocol/disco#items");
859         }
860         else if (name != null && node == null) {
861             // Answer the features of a given room
862
// TODO lock the room while gathering this info???
863
MUCRoom room = getChatRoom(name);
864             if (room != null && canDiscoverRoom(room)) {
865                 features.add("http://jabber.org/protocol/muc");
866                 // Always add public since only public rooms can be discovered
867
features.add("muc_public");
868                 if (room.isMembersOnly()) {
869                     features.add("muc_membersonly");
870                 }
871                 else {
872                     features.add("muc_open");
873                 }
874                 if (room.isModerated()) {
875                     features.add("muc_moderated");
876                 }
877                 else {
878                     features.add("muc_unmoderated");
879                 }
880                 if (room.canAnyoneDiscoverJID()) {
881                     features.add("muc_nonanonymous");
882                 }
883                 else {
884                     features.add("muc_semianonymous");
885                 }
886                 if (room.isPasswordProtected()) {
887                     features.add("muc_passwordprotected");
888                 }
889                 else {
890                     features.add("muc_unsecured");
891                 }
892                 if (room.isPersistent()) {
893                     features.add("muc_persistent");
894                 }
895                 else {
896                     features.add("muc_temporary");
897                 }
898             }
899         }
900         return features.iterator();
901     }
902
903     public XDataFormImpl getExtendedInfo(String JavaDoc name, String JavaDoc node, JID senderJID) {
904         if (name != null && node == null) {
905             // Answer the extended info of a given room
906
// TODO lock the room while gathering this info???
907
MUCRoom room = getChatRoom(name);
908             if (room != null && canDiscoverRoom(room)) {
909                 XDataFormImpl dataForm = new XDataFormImpl(DataForm.TYPE_RESULT);
910
911                 XFormFieldImpl field = new XFormFieldImpl("FORM_TYPE");
912                 field.setType(FormField.TYPE_HIDDEN);
913                 field.addValue("http://jabber.org/protocol/muc#roominfo");
914                 dataForm.addField(field);
915
916                 field = new XFormFieldImpl("muc#roominfo_description");
917                 field.setLabel(LocaleUtils.getLocalizedString("muc.extended.info.desc"));
918                 field.addValue(room.getDescription());
919                 dataForm.addField(field);
920
921                 field = new XFormFieldImpl("muc#roominfo_subject");
922                 field.setLabel(LocaleUtils.getLocalizedString("muc.extended.info.subject"));
923                 field.addValue(room.getSubject());
924                 dataForm.addField(field);
925
926                 field = new XFormFieldImpl("muc#roominfo_occupants");
927                 field.setLabel(LocaleUtils.getLocalizedString("muc.extended.info.occupants"));
928                 field.addValue(Integer.toString(room.getOccupantsCount()));
929                 dataForm.addField(field);
930
931                 /*field = new XFormFieldImpl("muc#roominfo_lang");
932                 field.setLabel(LocaleUtils.getLocalizedString("muc.extended.info.language"));
933                 field.addValue(room.getLanguage());
934                 dataForm.addField(field);*/

935
936                 field = new XFormFieldImpl("x-muc#roominfo_creationdate");
937                 field.setLabel(LocaleUtils.getLocalizedString("muc.extended.info.creationdate"));
938                 field.addValue(dateFormatter.format(room.getCreationDate()));
939                 dataForm.addField(field);
940
941                 return dataForm;
942             }
943         }
944         return null;
945     }
946
947     public boolean hasInfo(String JavaDoc name, String JavaDoc node, JID senderJID) {
948         if (name == null && node == node) {
949             // We always have info about the MUC service
950
return true;
951         }
952         else if (name != null && node == null) {
953             // We only have info if the room exists
954
return hasChatRoom(name);
955         }
956         else if (name != null && "x-roomuser-item".equals(node)) {
957             // We always have info about reserved names as long as the room exists
958
return hasChatRoom(name);
959         }
960         return false;
961     }
962
963     public Iterator<Element> getItems(String JavaDoc name, String JavaDoc node, JID senderJID) {
964         List JavaDoc<Element> answer = new ArrayList JavaDoc<Element>();
965         if (name == null && node == null) {
966             Element item;
967             // Answer all the public rooms as items
968
for (MUCRoom room : rooms.values()) {
969                 if (canDiscoverRoom(room)) {
970                     item = DocumentHelper.createElement("item");
971                     item.addAttribute("jid", room.getRole().getRoleAddress().toString());
972                     item.addAttribute("name", room.getNaturalLanguageName());
973
974                     answer.add(item);
975                 }
976             }
977         }
978         else if (name != null && node == null) {
979             // Answer the room occupants as items if that info is publicly available
980
MUCRoom room = getChatRoom(name);
981             if (room != null && canDiscoverRoom(room)) {
982                 Element item;
983                 for (MUCRole role : room.getOccupants()) {
984                     // TODO Should we filter occupants that are invisible (presence is not broadcasted)?
985
item = DocumentHelper.createElement("item");
986                     item.addAttribute("jid", role.getRoleAddress().toString());
987
988                     answer.add(item);
989                 }
990             }
991         }
992         return answer.iterator();
993     }
994
995     private boolean canDiscoverRoom(MUCRoom room) {
996         // Check if locked rooms may be discovered
997
if (!allowToDiscoverLockedRooms && room.isLocked()) {
998             return false;
999         }
1000        return room.isPublicRoom();
1001    }
1002
1003    /**
1004     * Converts an array to a comma-delimitted String.
1005     *
1006     * @param array the array.
1007     * @return a comma delimtted String of the array values.
1008     */

1009    private static String JavaDoc fromArray(String JavaDoc [] array) {
1010        StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
1011        for (int i=0; i<array.length; i++) {
1012            buf.append(array[i]);
1013            if (i != array.length-1) {
1014                buf.append(",");
1015            }
1016        }
1017        return buf.toString();
1018    }
1019}
Popular Tags