KickJava   Java API By Example, From Geeks To Geeks.

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


1 /**
2  * $RCSfile: MUCRoomImpl.java,v $
3  * $Revision: 1.40 $
4  * $Date: 2005/07/12 21:40:16 $
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.locks.ReadWriteLock JavaDoc;
18 import java.util.concurrent.locks.ReentrantReadWriteLock JavaDoc;
19
20 import org.jivesoftware.database.SequenceManager;
21 import org.jivesoftware.messenger.muc.*;
22 import org.jivesoftware.util.*;
23 import org.jivesoftware.messenger.*;
24 import org.jivesoftware.messenger.auth.UnauthorizedException;
25 import org.jivesoftware.messenger.user.UserAlreadyExistsException;
26 import org.jivesoftware.messenger.user.UserNotFoundException;
27 import org.xmpp.packet.*;
28 import org.dom4j.Element;
29
30 /**
31  * Simple in-memory implementation of a chatroom. A MUCRoomImpl could represent a persistent room
32  * which means that its configuration will be maintained in synch with its representation in the
33  * database.
34  *
35  * @author Gaston Dombiak
36  */

37 public class MUCRoomImpl implements MUCRoom {
38
39     /**
40      * The server hosting the room.
41      */

42     private MultiUserChatServer server;
43
44     /**
45      * The occupants of the room accessible by the occupants nickname.
46      */

47     private Map JavaDoc<String JavaDoc,MUCRole> occupants = new ConcurrentHashMap JavaDoc<String JavaDoc, MUCRole>();
48
49     /**
50      * The occupants of the room accessible by the occupants bare JID.
51      */

52     private Map JavaDoc<String JavaDoc, List JavaDoc<MUCRole>> occupantsByBareJID = new ConcurrentHashMap JavaDoc<String JavaDoc, List JavaDoc<MUCRole>>();
53
54     /**
55      * The occupants of the room accessible by the occupants full JID.
56      */

57     private Map JavaDoc<JID, MUCRole> occupantsByFullJID = new ConcurrentHashMap JavaDoc<JID, MUCRole>();
58
59     /**
60      * The name of the room.
61      */

62     private String JavaDoc name;
63
64     /**
65      * A lock to protect the room occupants.
66      */

67     ReadWriteLock JavaDoc lock = new ReentrantReadWriteLock JavaDoc();
68
69     /**
70      * The role of the room itself.
71      */

72     private MUCRole role;
73
74     /**
75      * The router used to send packets for the room.
76      */

77     private PacketRouter router;
78
79     /**
80      * The start time of the chat.
81      */

82     long startTime;
83
84     /**
85      * The end time of the chat.
86      */

87     long endTime;
88
89     /**
90      * After a room has been destroyed it may remain in memory but it won't be possible to use it.
91      * When a room is destroyed it is immediately removed from the MultiUserChatServer but it's
92      * possible that while the room was being destroyed it was being used by another thread so we
93      * need to protect the room under these rare circumstances.
94      */

95     boolean isDestroyed = false;
96
97     /**
98      * ChatRoomHistory object.
99      */

100     private MUCRoomHistory roomHistory;
101
102     /**
103      * Time when the room was locked. A value of zero means that the room is unlocked.
104      */

105     private long lockedTime;
106
107     /**
108      * List of chatroom's owner. The list contains only bare jid.
109      */

110     List JavaDoc<String JavaDoc> owners = new CopyOnWriteArrayList JavaDoc<String JavaDoc>();
111
112     /**
113      * List of chatroom's admin. The list contains only bare jid.
114      */

115     List JavaDoc<String JavaDoc> admins = new CopyOnWriteArrayList JavaDoc<String JavaDoc>();
116
117     /**
118      * List of chatroom's members. The list contains only bare jid.
119      */

120     private Map JavaDoc<String JavaDoc, String JavaDoc> members = new ConcurrentHashMap JavaDoc<String JavaDoc,String JavaDoc>();
121
122     /**
123      * List of chatroom's outcast. The list contains only bare jid of not allowed users.
124      */

125     private List JavaDoc<String JavaDoc> outcasts = new CopyOnWriteArrayList JavaDoc<String JavaDoc>();
126
127     /**
128      * The natural language name of the room.
129      */

130     private String JavaDoc naturalLanguageName;
131
132     /**
133      * Description of the room. The owner can change the description using the room configuration
134      * form.
135      */

136     private String JavaDoc description;
137
138     /**
139      * Indicates if occupants are allowed to change the subject of the room.
140      */

141     private boolean canOccupantsChangeSubject = false;
142
143     /**
144      * Maximum number of occupants that could be present in the room. If the limit's been reached
145      * and a user tries to join, a not-allowed error will be returned.
146      */

147     private int maxUsers = 30;
148
149     /**
150      * List of roles of which presence will be broadcasted to the rest of the occupants. This
151      * feature is useful for implementing "invisible" occupants.
152      */

153     private List JavaDoc<String JavaDoc> rolesToBroadcastPresence = new ArrayList JavaDoc<String JavaDoc>();
154
155     /**
156      * A public room means that the room is searchable and visible. This means that the room can be
157      * located using disco requests.
158      */

159     private boolean publicRoom = true;
160
161     /**
162      * Persistent rooms are saved to the database to make sure that rooms configurations can be
163      * restored in case the server goes down.
164      */

165     private boolean persistent = false;
166
167     /**
168      * Moderated rooms enable only participants to speak. Users that join the room and aren't
169      * participants can't speak (they are just visitors).
170      */

171     private boolean moderated = false;
172
173     /**
174      * A room is considered members-only if an invitation is required in order to enter the room.
175      * Any user that is not a member of the room won't be able to join the room unless the user
176      * decides to register with the room (thus becoming a member).
177      */

178     private boolean membersOnly = false;
179
180     /**
181      * Some rooms may restrict the occupants that are able to send invitations. Sending an
182      * invitation in a members-only room adds the invitee to the members list.
183      */

184     private boolean canOccupantsInvite = false;
185
186     /**
187      * The password that every occupant should provide in order to enter the room.
188      */

189     private String JavaDoc password = null;
190
191     /**
192      * Every presence packet can include the JID of every occupant unless the owner deactives this
193      * configuration.
194      */

195     private boolean canAnyoneDiscoverJID = false;
196
197     /**
198      * Enables the logging of the conversation. The conversation in the room will be saved to the
199      * database.
200      */

201     private boolean logEnabled = false;
202
203     /**
204      * Enables the logging of the conversation. The conversation in the room will be saved to the
205      * database.
206      */

207     private boolean loginRestrictedToNickname = false;
208
209     /**
210      * Enables the logging of the conversation. The conversation in the room will be saved to the
211      * database.
212      */

213     private boolean canChangeNickname = true;
214
215     /**
216      * Enables the logging of the conversation. The conversation in the room will be saved to the
217      * database.
218      */

219     private boolean registrationEnabled = true;
220
221     /**
222      * Internal component that handles IQ packets sent by the room owners.
223      */

224     private IQOwnerHandler iqOwnerHandler;
225
226     /**
227      * Internal component that handles IQ packets sent by moderators, admins and owners.
228      */

229     private IQAdminHandler iqAdminHandler;
230
231     /**
232      * The last known subject of the room. This information is used to respond disco requests. The
233      * MUCRoomHistory class holds the history of the room together with the last message that set
234      * the room's subject.
235      */

236     private String JavaDoc subject = "";
237     
238     /**
239      * The ID of the room. If the room is temporary and does not log its conversation then the value
240      * will always be -1. Otherwise a value will be obtained from the database.
241      */

242     private long roomID = -1;
243
244     /**
245      * The date when the room was created.
246      */

247     private Date creationDate;
248
249     /**
250      * The last date when the room's configuration was modified.
251      */

252     private Date modificationDate;
253
254     /**
255      * The date when the last occupant left the room. A null value means that there are occupants
256      * in the room at the moment.
257      */

258     private Date emptyDate;
259
260     /**
261      * Indicates if the room is present in the database.
262      */

263     private boolean savedToDB = false;
264
265     /**
266      * Create a new chat room.
267      *
268      * @param chatserver the server hosting the room.
269      * @param roomname the name of the room.
270      * @param packetRouter the router for sending packets from the room.
271      */

272     MUCRoomImpl(MultiUserChatServer chatserver, String JavaDoc roomname, PacketRouter packetRouter) {
273         this.server = chatserver;
274         this.name = roomname;
275         this.naturalLanguageName = roomname;
276         this.description = roomname;
277         this.router = packetRouter;
278         this.startTime = System.currentTimeMillis();
279         this.creationDate = new Date(startTime);
280         this.modificationDate = new Date(startTime);
281         this.emptyDate = new Date(startTime);
282         // TODO Allow to set the history strategy from the configuration form?
283
roomHistory = new MUCRoomHistory(this, new HistoryStrategy(server.getHistoryStrategy()));
284         role = new RoomRole(this);
285         this.iqOwnerHandler = new IQOwnerHandler(this, packetRouter);
286         this.iqAdminHandler = new IQAdminHandler(this, packetRouter);
287         // No one can join the room except the room's owner
288
this.lockedTime = startTime;
289         // Set the default roles for which presence is broadcast
290
rolesToBroadcastPresence.add("moderator");
291         rolesToBroadcastPresence.add("participant");
292         rolesToBroadcastPresence.add("visitor");
293     }
294
295     public String JavaDoc getName() {
296         return name;
297     }
298
299     public long getID() {
300         if (isPersistent() || isLogEnabled()) {
301             if (roomID == -1) {
302                 roomID = SequenceManager.nextID(JiveConstants.MUC_ROOM);
303             }
304         }
305         return roomID;
306     }
307
308     public void setID(long roomID) {
309         this.roomID = roomID;
310     }
311
312     public Date getCreationDate() {
313         return creationDate;
314     }
315
316     public void setCreationDate(Date creationDate) {
317         this.creationDate = creationDate;
318     }
319
320     public Date getModificationDate() {
321         return modificationDate;
322     }
323
324     public void setModificationDate(Date modificationDate) {
325         this.modificationDate = modificationDate;
326     }
327
328     public void setEmptyDate(Date emptyDate) {
329         // Do nothing if old value is same as new value
330
if (this.emptyDate == emptyDate) {
331             return;
332         }
333         this.emptyDate = emptyDate;
334         MUCPersistenceManager.updateRoomEmptyDate(this);
335     }
336
337     public Date getEmptyDate() {
338         return this.emptyDate;
339     }
340
341     public MUCRole getRole() {
342         return role;
343     }
344
345     public MUCRole getOccupant(String JavaDoc nickname) throws UserNotFoundException {
346         if (nickname == null) {
347              throw new UserNotFoundException();
348         }
349         MUCRole role = occupants.get(nickname.toLowerCase());
350         if (role != null) {
351             return role;
352         }
353         throw new UserNotFoundException();
354     }
355
356     public List JavaDoc<MUCRole> getOccupantsByBareJID(String JavaDoc jid) throws UserNotFoundException {
357         List JavaDoc<MUCRole> roles = occupantsByBareJID.get(jid);
358         if (roles != null && !roles.isEmpty()) {
359             return Collections.unmodifiableList(roles);
360         }
361         throw new UserNotFoundException();
362     }
363
364     public MUCRole getOccupantByFullJID(String JavaDoc jid) throws UserNotFoundException {
365         MUCRole role = occupantsByFullJID.get(jid);
366         if (role != null) {
367             return role;
368         }
369         throw new UserNotFoundException();
370     }
371
372     public Collection<MUCRole> getOccupants() {
373         return Collections.unmodifiableCollection(occupants.values());
374     }
375
376     public int getOccupantsCount() {
377         return occupants.size();
378     }
379
380     public boolean hasOccupant(String JavaDoc nickname) {
381         return occupants.containsKey(nickname.toLowerCase());
382     }
383
384     public String JavaDoc getReservedNickname(String JavaDoc bareJID) {
385         String JavaDoc answer = members.get(bareJID);
386         if (answer == null || answer.trim().length() == 0) {
387             return null;
388         }
389         return answer;
390     }
391
392     public MUCRole.Affiliation getAffiliation(String JavaDoc bareJID) {
393         if (owners.contains(bareJID)) {
394             return MUCRole.Affiliation.owner;
395         }
396         else if (admins.contains(bareJID)) {
397             return MUCRole.Affiliation.admin;
398         }
399         else if (members.containsKey(bareJID)) {
400             return MUCRole.Affiliation.member;
401         }
402         else if (outcasts.contains(bareJID)) {
403             return MUCRole.Affiliation.outcast;
404         }
405         return MUCRole.Affiliation.none;
406     }
407
408     public MUCRole joinRoom(String JavaDoc nickname, String JavaDoc password, HistoryRequest historyRequest,
409             MUCUser user, Presence presence) throws UnauthorizedException,
410             UserAlreadyExistsException, RoomLockedException, ForbiddenException,
411             RegistrationRequiredException, ConflictException, ServiceUnavailableException,
412             NotAcceptableException {
413         MUCRoleImpl joinRole = null;
414         lock.writeLock().lock();
415         try {
416             // If the room has a limit of max user then check if the limit has been reached
417
if (isDestroyed || (getMaxUsers() > 0 && getOccupantsCount() >= getMaxUsers())) {
418                 throw new ServiceUnavailableException();
419             }
420             boolean isOwner = owners.contains(user.getAddress().toBareJID());
421             // If the room is locked and this user is not an owner raise a RoomLocked exception
422
if (isLocked()) {
423                 if (!isOwner) {
424                     throw new RoomLockedException();
425                 }
426             }
427             // If the user is already in the room raise a UserAlreadyExists exception
428
if (occupants.containsKey(nickname.toLowerCase())) {
429                 throw new UserAlreadyExistsException();
430             }
431             // If the room is password protected and the provided password is incorrect raise a
432
// Unauthorized exception
433
if (isPasswordProtected()) {
434                 if (password == null || !password.equals(getPassword())) {
435                     throw new UnauthorizedException();
436                 }
437             }
438             // If another user attempts to join the room with a nickname reserved by the first user
439
// raise a ConflictException
440
if (members.containsValue(nickname)) {
441                 if (!nickname.equals(members.get(user.getAddress().toBareJID()))) {
442                     throw new ConflictException();
443                 }
444             }
445             if (isLoginRestrictedToNickname()) {
446                 String JavaDoc reservedNickname = members.get(user.getAddress().toBareJID());
447                 if (reservedNickname != null && !nickname.equals(reservedNickname)) {
448                     throw new NotAcceptableException();
449                 }
450             }
451
452             // Set the corresponding role based on the user's affiliation
453
MUCRole.Role role;
454             MUCRole.Affiliation affiliation;
455             if (isOwner) {
456                 // The user is an owner. Set the role and affiliation accordingly.
457
role = MUCRole.Role.moderator;
458                 affiliation = MUCRole.Affiliation.owner;
459             }
460             else if (server.getSysadmins().contains(user.getAddress().toBareJID())) {
461                 // The user is a system administrator of the MUC service. Treat him as an owner
462
// although he won't appear in the list of owners
463
role = MUCRole.Role.moderator;
464                 affiliation = MUCRole.Affiliation.owner;
465             }
466             else if (admins.contains(user.getAddress().toBareJID())) {
467                 // The user is an admin. Set the role and affiliation accordingly.
468
role = MUCRole.Role.moderator;
469                 affiliation = MUCRole.Affiliation.admin;
470             }
471             else if (members.containsKey(user.getAddress().toBareJID())) {
472                 // The user is a member. Set the role and affiliation accordingly.
473
role = MUCRole.Role.participant;
474                 affiliation = MUCRole.Affiliation.member;
475             }
476             else if (outcasts.contains(user.getAddress().toBareJID())) {
477                 // The user is an outcast. Raise a "Forbidden" exception.
478
throw new ForbiddenException();
479             }
480             else {
481                 // The user has no affiliation (i.e. NONE). Set the role accordingly.
482
if (isMembersOnly()) {
483                     // The room is members-only and the user is not a member. Raise a
484
// "Registration Required" exception.
485
throw new RegistrationRequiredException();
486                 }
487                 role = (isModerated() ? MUCRole.Role.visitor : MUCRole.Role.participant);
488                 affiliation = MUCRole.Affiliation.none;
489             }
490             // Create a new role for this user in this room
491
joinRole =
492                     new MUCRoleImpl(server, this, nickname, role, affiliation, (MUCUserImpl) user,
493                             presence, router);
494             // Add the new user as an occupant of this room
495
occupants.put(nickname.toLowerCase(), joinRole);
496             // Update the tables of occupants based on the bare and full JID
497
List JavaDoc<MUCRole> list = occupantsByBareJID.get(user.getAddress().toBareJID());
498             if (list == null) {
499                 list = new ArrayList JavaDoc<MUCRole>();
500                 occupantsByBareJID.put(user.getAddress().toBareJID(), list);
501             }
502             list.add(joinRole);
503             occupantsByFullJID.put(user.getAddress(), joinRole);
504         }
505         finally {
506             lock.writeLock().unlock();
507         }
508         // Send presence of existing occupants to new occupant
509
sendInitialPresences(joinRole);
510         // It is assumed that the room is new based on the fact that it's locked and
511
// that it was locked when it was created.
512
boolean isRoomNew = isLocked() && creationDate.getTime() == lockedTime;
513         try {
514             // Send the presence of this new occupant to existing occupants
515
Presence joinPresence = joinRole.getPresence().createCopy();
516             if (isRoomNew) {
517                 Element frag = joinPresence.getChildElement(
518                         "x", "http://jabber.org/protocol/muc#user");
519                 frag.addElement("status").addAttribute("code", "201");
520             }
521             broadcastPresence(joinPresence);
522         }
523         catch (Exception JavaDoc e) {
524             Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
525         }
526         // If the room has just been created send the "room locked until configuration is
527
// confirmed" message
528
if (isRoomNew) {
529             Message message = new Message();
530             message.setType(Message.Type.groupchat);
531             message.setBody(LocaleUtils.getLocalizedString("muc.new"));
532             message.setFrom(role.getRoleAddress());
533             joinRole.send(message);
534         }
535         else if (isLocked()) {
536             // Warn the owner that the room is locked but it's not new
537
Message message = new Message();
538             message.setType(Message.Type.groupchat);
539             message.setBody(LocaleUtils.getLocalizedString("muc.locked"));
540             message.setFrom(role.getRoleAddress());
541             joinRole.send(message);
542         }
543         else if (canAnyoneDiscoverJID()) {
544             // Warn the new occupant that the room is non-anonymous (i.e. his JID will be
545
// public)
546
Message message = new Message();
547             message.setType(Message.Type.groupchat);
548             message.setBody(LocaleUtils.getLocalizedString("muc.warnnonanonymous"));
549             message.setFrom(role.getRoleAddress());
550             Element frag = message.addChildElement("x", "http://jabber.org/protocol/muc#user");
551             frag.addElement("status").addAttribute("code", "100");
552             joinRole.send(message);
553         }
554         if (historyRequest == null) {
555             Iterator history = roomHistory.getMessageHistory();
556             while (history.hasNext()) {
557                 joinRole.send((Message) history.next());
558             }
559         }
560         else {
561             historyRequest.sendHistory(joinRole, roomHistory);
562         }
563         // Update the date when the last occupant left the room
564
setEmptyDate(null);
565         return joinRole;
566     }
567
568     /**
569      * Sends presence of existing occupants to new occupant.
570      *
571      * @param joinRole the role of the new occupant in the room.
572      */

573     private void sendInitialPresences(MUCRoleImpl joinRole) {
574         for (MUCRole occupant : occupants.values()) {
575             if (occupant == joinRole) {
576                 continue;
577             }
578             Presence occupantPresence = occupant.getPresence().createCopy();
579             // Skip to the next occupant if we cannot send presence of this occupant
580
if (hasToCheckRoleToBroadcastPresence()) {
581                 Element frag = occupantPresence.getChildElement("x",
582                         "http://jabber.org/protocol/muc#user");
583                 // Check if we can broadcast the presence for this role
584
if (!canBroadcastPresence(frag.element("item").attributeValue("role"))) {
585                     continue;
586                 }
587             }
588             // Don't include the occupant's JID if the room is semi-anon and the new occupant
589
// is not a moderator
590
if (!canAnyoneDiscoverJID() && MUCRole.Role.moderator != joinRole.getRole()) {
591                 Element frag = occupantPresence.getChildElement("x",
592                         "http://jabber.org/protocol/muc#user");
593                 frag.element("item").addAttribute("jid", null);
594             }
595             joinRole.send(occupantPresence);
596         }
597     }
598
599     public void leaveRoom(String JavaDoc nickname) throws UserNotFoundException {
600         MUCRole leaveRole = null;
601         lock.writeLock().lock();
602         try {
603             leaveRole = occupants.remove(nickname.toLowerCase());
604             if (leaveRole == null) {
605                 throw new UserNotFoundException();
606             }
607             // Removes the role from the room
608
removeOccupantRole(leaveRole);
609
610             // TODO Implement this: If the room owner becomes unavailable for any reason before
611
// submitting the form (e.g., a lost connection), the service will receive a presence
612
// stanza of type "unavailable" from the owner to the room@service/nick or room@service
613
// (or both). The service MUST then destroy the room, sending a presence stanza of type
614
// "unavailable" from the room to the owner including a <destroy/> element and reason
615
// (if provided) as defined under the "Destroying a Room" use case.
616

617             // Remove the room from the server only if there are no more occupants and the room is
618
// not persistent
619
if (occupants.isEmpty() && !isPersistent()) {
620                 endTime = System.currentTimeMillis();
621                 server.removeChatRoom(name);
622             }
623             if (occupants.isEmpty()) {
624                 // Update the date when the last occupant left the room
625
setEmptyDate(new Date());
626             }
627         }
628         finally {
629             lock.writeLock().unlock();
630         }
631
632         try {
633             Presence presence = leaveRole.getPresence().createCopy();
634             presence.setType(Presence.Type.unavailable);
635             presence.setStatus(null);
636             // Inform the leaving user that he/she has left the room
637
leaveRole.send(presence);
638             // Inform the rest of the room occupants that the user has left the room
639
broadcastPresence(presence);
640         }
641         catch (Exception JavaDoc e) {
642             Log.error(e);
643         }
644     }
645
646     /**
647      * Removes the role of the occupant from all the internal occupants collections. The role will
648      * also be removed from the user's roles.
649      *
650      * @param leaveRole the role to remove.
651      */

652     private void removeOccupantRole(MUCRole leaveRole) {
653         occupants.remove(leaveRole.getNickname().toLowerCase());
654
655         MUCUser user = leaveRole.getChatUser();
656         // Notify the user that he/she is no longer in the room
657
user.removeRole(getName());
658         // Update the tables of occupants based on the bare and full JID
659
List JavaDoc list = occupantsByBareJID.get(user.getAddress().toBareJID());
660         if (list != null) {
661             list.remove(leaveRole);
662             if (list.isEmpty()) {
663                 occupantsByBareJID.remove(user.getAddress().toBareJID());
664             }
665         }
666         occupantsByFullJID.remove(user.getAddress().toString());
667     }
668
669     public void destroyRoom(String JavaDoc alternateJID, String JavaDoc reason) {
670         MUCRole leaveRole = null;
671         Collection<MUCRole> removedRoles = new ArrayList JavaDoc<MUCRole>();
672         lock.writeLock().lock();
673         try {
674             // Remove each occupant
675
for (String JavaDoc nickname: occupants.keySet()) {
676                 leaveRole = occupants.remove(nickname);
677
678                 if (leaveRole != null) {
679                     // Add the removed occupant to the list of removed occupants. We are keeping a
680
// list of removed occupants to process later outside of the lock.
681
removedRoles.add(leaveRole);
682                     removeOccupantRole(leaveRole);
683                 }
684             }
685             endTime = System.currentTimeMillis();
686             // Removes the room from the list of rooms hosted in the server
687
server.removeChatRoom(name);
688             // Set that the room has been destroyed
689
isDestroyed = true;
690         }
691         finally {
692             lock.writeLock().unlock();
693         }
694         // Send an unavailable presence to each removed occupant
695
for (MUCRole removedRole : removedRoles) {
696             try {
697                 // Send a presence stanza of type "unavailable" to the occupant
698
Presence presence = createPresence(Presence.Type.unavailable);
699                 presence.setFrom(removedRole.getRoleAddress());
700
701                 // A fragment containing the x-extension for room destruction.
702
Element fragment = presence.addChildElement("x",
703                         "http://jabber.org/protocol/muc#user");
704                 Element item = fragment.addElement("item");
705                 item.addAttribute("affiliation", "none");
706                 item.addAttribute("role", "none");
707                 if (alternateJID != null && alternateJID.length() > 0) {
708                     fragment.addElement("destroy").addAttribute("jid", alternateJID);
709                 }
710                 if (reason != null && reason.length() > 0) {
711                     Element destroy = fragment.element("destroy");
712                     if (destroy == null) {
713                         destroy = fragment.addElement("destroy");
714                     }
715                     destroy.addElement("reason").setText(reason);
716                 }
717                 removedRole.send(presence);
718             }
719             catch (Exception JavaDoc e) {
720                 Log.error(e);
721             }
722         }
723         // Remove the room from the DB if the room was persistent
724
MUCPersistenceManager.deleteFromDB(this);
725     }
726
727     public Presence createPresence(Presence.Type presenceType) throws UnauthorizedException {
728         Presence presence = new Presence();
729         presence.setType(presenceType);
730         presence.setFrom(role.getRoleAddress());
731         return presence;
732     }
733
734     public void serverBroadcast(String JavaDoc msg) {
735         Message message = new Message();
736         message.setType(Message.Type.groupchat);
737         message.setBody(msg);
738         message.setFrom(role.getRoleAddress());
739         roomHistory.addMessage(message);
740         broadcast(message);
741     }
742
743     public void sendPublicMessage(Message message, MUCRole senderRole) throws ForbiddenException {
744         // Check that if the room is moderated then the sender of the message has to have voice
745
if (isModerated() && senderRole.getRole().compareTo(MUCRole.Role.participant) > 0) {
746             throw new ForbiddenException();
747         }
748         // Send the message to all occupants
749
message.setFrom(senderRole.getRoleAddress());
750         send(message);
751     }
752
753     public void sendPrivateMessage(Message message, MUCRole senderRole) throws NotFoundException {
754         String JavaDoc resource = message.getTo().getResource();
755         MUCRole occupant = occupants.get(resource.toLowerCase());
756         if (occupant != null) {
757             message.setFrom(senderRole.getRoleAddress());
758             occupant.send(message);
759         }
760         else {
761             throw new NotFoundException();
762         }
763     }
764
765     public void send(Packet packet) {
766         if (packet instanceof Message) {
767             roomHistory.addMessage((Message)packet);
768             broadcast((Message)packet);
769         }
770         else if (packet instanceof Presence) {
771             broadcastPresence((Presence)packet);
772         }
773         else if (packet instanceof IQ) {
774             IQ reply = IQ.createResultIQ((IQ) packet);
775             reply.setChildElement(((IQ) packet).getChildElement());
776             reply.setError(PacketError.Condition.bad_request);
777             router.route(reply);
778         }
779     }
780
781     /**
782      * Broadcasts the specified presence to all room occupants. If the presence belongs to a
783      * user whose role cannot be broadcast then the presence will only be sent to the presence's
784      * user. On the other hand, the JID of the user that sent the presence won't be included if the
785      * room is semi-anon and the target occupant is not a moderator.
786      *
787      * @param presence the presence to broadcast.
788      */

789     private void broadcastPresence(Presence presence) {
790         if (presence == null) {
791             return;
792         }
793         Element frag = null;
794         String JavaDoc jid = null;
795         if (hasToCheckRoleToBroadcastPresence()) {
796             frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
797             // Check if we can broadcast the presence for this role
798
if (!canBroadcastPresence(frag.element("item").attributeValue("role"))) {
799                 // Just send the presence to the sender of the presence
800
try {
801                     MUCRole occupant = getOccupant(presence.getFrom().getResource());
802                     occupant.send(presence);
803                 }
804                 catch (UserNotFoundException e) {
805                     // Do nothing
806
}
807                 return;
808             }
809         }
810
811         // Don't include the occupant's JID if the room is semi-anon and the new occupant
812
// is not a moderator
813
if (!canAnyoneDiscoverJID()) {
814             if (frag == null) {
815                 frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
816             }
817             jid = frag.element("item").attributeValue("jid");
818         }
819         for (MUCRole occupant : occupants.values()) {
820             // Don't include the occupant's JID if the room is semi-anon and the new occupant
821
// is not a moderator
822
if (!canAnyoneDiscoverJID()) {
823                 if (MUCRole.Role.moderator == occupant.getRole()) {
824                     frag.element("item").addAttribute("jid", jid);
825                 }
826                 else {
827                     frag.element("item").addAttribute("jid", null);
828                 }
829             }
830             occupant.send(presence);
831         }
832     }
833
834     private void broadcast(Message message) {
835         for (MUCRole occupant : occupants.values()) {
836             occupant.send(message);
837         }
838         if (isLogEnabled()) {
839             MUCRole senderRole = null;
840             JID senderAddress = null;
841             if (message.getTo() != null && message.getTo().getResource() != null) {
842                 senderRole = occupants.get(message.getTo().getResource());
843             }
844             if (senderRole == null) {
845                 // The room itself is sending the message
846
senderAddress = getRole().getRoleAddress();
847             }
848             else {
849                 // An occupant is sending the message
850
senderAddress = senderRole.getChatUser().getAddress();
851             }
852             // Log the conversation
853
server.logConversation(this, message, senderAddress);
854         }
855     }
856
857     /**
858      * An empty role that represents the room itself in the chatroom. Chatrooms need to be able to
859      * speak (server messages) and so must have their own role in the chatroom.
860      */

861     private class RoomRole implements MUCRole {
862
863         private MUCRoom room;
864
865         private RoomRole(MUCRoom room) {
866             this.room = room;
867         }
868
869         public Presence getPresence() {
870             return null;
871         }
872
873         public Element getExtendedPresenceInformation() {
874             return null;
875         }
876
877         public void setPresence(Presence presence) {
878         }
879
880         public void setRole(MUCRole.Role newRole) {
881         }
882
883         public MUCRole.Role getRole() {
884             return MUCRole.Role.moderator;
885         }
886
887         public void setAffiliation(MUCRole.Affiliation newAffiliation) {
888         }
889
890         public MUCRole.Affiliation getAffiliation() {
891             return MUCRole.Affiliation.owner;
892         }
893
894         public String JavaDoc getNickname() {
895             return null;
896         }
897
898         public MUCUser getChatUser() {
899             return null;
900         }
901
902         public MUCRoom getChatRoom() {
903             return room;
904         }
905
906         private JID crJID = null;
907
908         public JID getRoleAddress() {
909             if (crJID == null) {
910                 crJID = new JID(room.getName(), server.getServiceDomain(), "");
911             }
912             return crJID;
913         }
914
915         public void send(Packet packet) {
916             room.send(packet);
917         }
918
919         public void changeNickname(String JavaDoc nickname) {
920         }
921     }
922
923     public long getChatLength() {
924         return endTime - startTime;
925     }
926
927     /**
928      * Updates all the presences of the given user with the new affiliation and role information. Do
929      * nothing if the given jid is not present in the room. If the user has joined the room from
930      * several client resources, all his/her occupants' presences will be updated.
931      *
932      * @param bareJID the bare jid of the user to update his/her role.
933      * @param newAffiliation the new affiliation for the JID.
934      * @param newRole the new role for the JID.
935      * @return the list of updated presences of all the client resources that the client used to
936      * join the room.
937      * @throws NotAllowedException If trying to change the moderator role to an owner or an admin or
938      * if trying to ban an owner or an administrator.
939      */

940     private List JavaDoc<Presence> changeOccupantAffiliation(String JavaDoc bareJID, MUCRole.Affiliation newAffiliation, MUCRole.Role newRole)
941             throws NotAllowedException {
942         List JavaDoc<Presence> presences = new ArrayList JavaDoc<Presence>();
943         // Get all the roles (i.e. occupants) of this user based on his/her bare JID
944
List JavaDoc<MUCRole> roles = occupantsByBareJID.get(bareJID);
945         if (roles == null) {
946             return presences;
947         }
948         // Collect all the updated presences of these roles
949
for (MUCRole role : roles) {
950             // Update the presence with the new affiliation and role
951
role.setAffiliation(newAffiliation);
952             role.setRole(newRole);
953             // Prepare a new presence to be sent to all the room occupants
954
presences.add(role.getPresence().createCopy());
955         }
956         // Answer all the updated presences
957
return presences;
958     }
959
960     /**
961      * Updates the presence of the given user with the new role information. Do nothing if the given
962      * jid is not present in the room.
963      *
964      * @param jid the full jid of the user to update his/her role.
965      * @param newRole the new role for the JID.
966      * @return the updated presence of the user or null if none.
967      * @throws NotAllowedException If trying to change the moderator role to an owner or an admin.
968      */

969     private Presence changeOccupantRole(JID jid, MUCRole.Role newRole) throws NotAllowedException {
970         // Try looking the role in the bare JID list
971
MUCRole role = occupantsByFullJID.get(jid);
972         if (role != null) {
973             // Update the presence with the new role
974
role.setRole(newRole);
975             // Prepare a new presence to be sent to all the room occupants
976
return role.getPresence().createCopy();
977         }
978         return null;
979     }
980
981     public void addFirstOwner(String JavaDoc bareJID) {
982         owners.add(bareJID);
983     }
984
985     public List JavaDoc<Presence> addOwner(String JavaDoc bareJID, MUCRole sendRole) throws ForbiddenException {
986         MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
987         if (MUCRole.Affiliation.owner != sendRole.getAffiliation()) {
988             throw new ForbiddenException();
989         }
990         owners.add(bareJID);
991         // Remove the user from other affiliation lists
992
if (removeAdmin(bareJID)) {
993             oldAffiliation = MUCRole.Affiliation.admin;
994         }
995         else if (removeMember(bareJID)) {
996             oldAffiliation = MUCRole.Affiliation.member;
997         }
998         else if (removeOutcast(bareJID)) {
999             oldAffiliation = MUCRole.Affiliation.outcast;
1000        }
1001        // Update the DB if the room is persistent
1002
MUCPersistenceManager.saveAffiliationToDB(
1003            this,
1004            bareJID,
1005            null,
1006            MUCRole.Affiliation.owner,
1007            oldAffiliation);
1008        // Update the presence with the new affiliation and inform all occupants
1009
try {
1010            return changeOccupantAffiliation(bareJID, MUCRole.Affiliation.owner,
1011                    MUCRole.Role.moderator);
1012        }
1013        catch (NotAllowedException e) {
1014            // We should never receive this exception....in theory
1015
return null;
1016        }
1017    }
1018
1019    private boolean removeOwner(String JavaDoc bareJID) {
1020        return owners.remove(bareJID);
1021    }
1022
1023    public List JavaDoc<Presence> addAdmin(String JavaDoc bareJID, MUCRole sendRole) throws ForbiddenException,
1024            ConflictException {
1025        MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
1026        if (MUCRole.Affiliation.owner != sendRole.getAffiliation()) {
1027            throw new ForbiddenException();
1028        }
1029        // Check that the room always has an owner
1030
if (owners.contains(bareJID) && owners.size() == 1) {
1031            throw new ConflictException();
1032        }
1033        admins.add(bareJID);
1034        // Remove the user from other affiliation lists
1035
if (removeOwner(bareJID)) {
1036            oldAffiliation = MUCRole.Affiliation.owner;
1037        }
1038        else if (removeMember(bareJID)) {
1039            oldAffiliation = MUCRole.Affiliation.member;
1040        }
1041        else if (removeOutcast(bareJID)) {
1042            oldAffiliation = MUCRole.Affiliation.outcast;
1043        }
1044        // Update the DB if the room is persistent
1045
MUCPersistenceManager.saveAffiliationToDB(
1046            this,
1047            bareJID,
1048            null,
1049            MUCRole.Affiliation.admin,
1050            oldAffiliation);
1051        // Update the presence with the new affiliation and inform all occupants
1052
try {
1053            return changeOccupantAffiliation(bareJID, MUCRole.Affiliation.admin,
1054                    MUCRole.Role.moderator);
1055        }
1056        catch (NotAllowedException e) {
1057            // We should never receive this exception....in theory
1058
return null;
1059        }
1060    }
1061
1062    private boolean removeAdmin(String JavaDoc bareJID) {
1063        return admins.remove(bareJID);
1064    }
1065
1066    public List JavaDoc<Presence> addMember(String JavaDoc bareJID, String JavaDoc nickname, MUCRole sendRole)
1067            throws ForbiddenException, ConflictException {
1068        MUCRole.Affiliation oldAffiliation = (members.containsKey(bareJID) ?
1069                MUCRole.Affiliation.member : MUCRole.Affiliation.none);
1070        if (isMembersOnly()) {
1071            if (!canOccupantsInvite()) {
1072                if (MUCRole.Affiliation.admin != sendRole.getAffiliation()
1073                        && MUCRole.Affiliation.owner != sendRole.getAffiliation()) {
1074                    throw new ForbiddenException();
1075                }
1076            }
1077        }
1078        else {
1079            if (MUCRole.Affiliation.admin != sendRole.getAffiliation()
1080                    && MUCRole.Affiliation.owner != sendRole.getAffiliation()) {
1081                throw new ForbiddenException();
1082            }
1083        }
1084        // Check if the desired nickname is already reserved for another member
1085
if (nickname != null && nickname.trim().length() > 0 && members.containsValue(nickname)) {
1086            if (!nickname.equals(members.get(bareJID))) {
1087                throw new ConflictException();
1088            }
1089        }
1090        // Check that the room always has an owner
1091
if (owners.contains(bareJID) && owners.size() == 1) {
1092            throw new ConflictException();
1093        }
1094        // Associate the reserved nickname with the bareJID. If nickname is null then associate an
1095
// empty string
1096
members.put(bareJID, (nickname == null ? "" : nickname));
1097        // Remove the user from other affiliation lists
1098
if (removeOwner(bareJID)) {
1099            oldAffiliation = MUCRole.Affiliation.owner;
1100        }
1101        else if (removeAdmin(bareJID)) {
1102            oldAffiliation = MUCRole.Affiliation.admin;
1103        }
1104        else if (removeOutcast(bareJID)) {
1105            oldAffiliation = MUCRole.Affiliation.outcast;
1106        }
1107        // Update the DB if the room is persistent
1108
MUCPersistenceManager.saveAffiliationToDB(
1109            this,
1110            bareJID,
1111            nickname,
1112            MUCRole.Affiliation.member,
1113            oldAffiliation);
1114        // Update the presence with the new affiliation and inform all occupants
1115
try {
1116            return changeOccupantAffiliation(bareJID, MUCRole.Affiliation.member,
1117                    MUCRole.Role.participant);
1118        }
1119        catch (NotAllowedException e) {
1120            // We should never receive this exception....in theory
1121
return null;
1122        }
1123    }
1124
1125    private boolean removeMember(String JavaDoc bareJID) {
1126        boolean answer = members.containsKey(bareJID);
1127        members.remove(bareJID);
1128        return answer;
1129    }
1130
1131    public List JavaDoc<Presence> addOutcast(String JavaDoc bareJID, String JavaDoc reason, MUCRole senderRole)
1132            throws NotAllowedException, ForbiddenException, ConflictException {
1133        MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
1134        if (MUCRole.Affiliation.admin != senderRole.getAffiliation()
1135                && MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
1136            throw new ForbiddenException();
1137        }
1138        // Check that the room always has an owner
1139
if (owners.contains(bareJID) && owners.size() == 1) {
1140            throw new ConflictException();
1141        }
1142        // Update the presence with the new affiliation and inform all occupants
1143
JID actorJID = null;
1144        // actorJID will be null if the room itself (ie. via admin console) made the request
1145
if (senderRole.getChatUser() != null) {
1146            actorJID = senderRole.getChatUser().getAddress();
1147        }
1148        List JavaDoc<Presence> updatedPresences = changeOccupantAffiliation(
1149                bareJID,
1150                MUCRole.Affiliation.outcast,
1151                MUCRole.Role.none);
1152        Element frag;
1153        // Add the status code and reason why the user was banned to the presences that will
1154
// be sent to the room occupants (the banned user will not receive this presences)
1155
for (Presence presence : updatedPresences) {
1156            frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
1157            // Add the status code 301 that indicates that the user was banned
1158
frag.addElement("status").addAttribute("code", "301");
1159            // Add the reason why the user was banned
1160
if (reason != null && reason.trim().length() > 0) {
1161                frag.element("item").addElement("reason").setText(reason);
1162            }
1163
1164            // Remove the banned users from the room. If a user has joined the room from
1165
// different client resources, he/she will be kicked from all the client resources
1166
// Effectively kick the occupant from the room
1167
kickPresence(presence, actorJID);
1168        }
1169        // Update the affiliation lists
1170
outcasts.add(bareJID);
1171        // Remove the user from other affiliation lists
1172
if (removeOwner(bareJID)) {
1173            oldAffiliation = MUCRole.Affiliation.owner;
1174        }
1175        else if (removeAdmin(bareJID)) {
1176            oldAffiliation = MUCRole.Affiliation.admin;
1177        }
1178        else if (removeMember(bareJID)) {
1179            oldAffiliation = MUCRole.Affiliation.member;
1180        }
1181        // Update the DB if the room is persistent
1182
MUCPersistenceManager.saveAffiliationToDB(
1183            this,
1184            bareJID,
1185            null,
1186            MUCRole.Affiliation.outcast,
1187            oldAffiliation);
1188        return updatedPresences;
1189    }
1190
1191    private boolean removeOutcast(String JavaDoc bareJID) {
1192        return outcasts.remove(bareJID);
1193    }
1194
1195    public List JavaDoc<Presence> addNone(String JavaDoc bareJID, MUCRole senderRole) throws ForbiddenException,
1196            ConflictException {
1197        MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
1198        if (MUCRole.Affiliation.admin != senderRole.getAffiliation()
1199                && MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
1200            throw new ForbiddenException();
1201        }
1202        // Check that the room always has an owner
1203
if (owners.contains(bareJID) && owners.size() == 1) {
1204            throw new ConflictException();
1205        }
1206        List JavaDoc<Presence> updatedPresences = null;
1207        boolean wasMember = members.containsKey(bareJID) || admins.contains(bareJID) ||
1208                owners.contains(bareJID);
1209        // Remove the user from ALL the affiliation lists
1210
if (removeOwner(bareJID)) {
1211            oldAffiliation = MUCRole.Affiliation.owner;
1212        }
1213        else if (removeAdmin(bareJID)) {
1214            oldAffiliation = MUCRole.Affiliation.admin;
1215        }
1216        else if (removeMember(bareJID)) {
1217            oldAffiliation = MUCRole.Affiliation.member;
1218        }
1219        else if (removeOutcast(bareJID)) {
1220            oldAffiliation = MUCRole.Affiliation.outcast;
1221        }
1222        // Remove the affiliation of this user from the DB if the room is persistent
1223
MUCPersistenceManager.removeAffiliationFromDB(this, bareJID, oldAffiliation);
1224
1225        // Update the presence with the new affiliation and inform all occupants
1226
try {
1227            MUCRole.Role newRole;
1228            if (isMembersOnly() && wasMember) {
1229                newRole = MUCRole.Role.none;
1230            }
1231            else {
1232                newRole = isModerated() ? MUCRole.Role.visitor : MUCRole.Role.participant;
1233            }
1234            updatedPresences = changeOccupantAffiliation(bareJID, MUCRole.Affiliation.none, newRole);
1235            if (isMembersOnly() && wasMember) {
1236                // If the room is members-only, remove the user from the room including a status
1237
// code of 321 to indicate that the user was removed because of an affiliation
1238
// change
1239
Element frag;
1240                // Add the status code to the presences that will be sent to the room occupants
1241
for (Presence presence : updatedPresences) {
1242                    // Set the presence as an unavailable presence
1243
presence.setType(Presence.Type.unavailable);
1244                    presence.setStatus(null);
1245                    frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
1246                    // Add the status code 321 that indicates that the user was removed because of
1247
// an affiliation change
1248
frag.addElement("status").addAttribute("code", "321");
1249
1250                    // Remove the ex-member from the room. If a user has joined the room from
1251
// different client resources, he/she will be kicked from all the client
1252
// resources.
1253
// Effectively kick the occupant from the room
1254
MUCUser senderUser = senderRole.getChatUser();
1255                    JID actorJID = (senderUser == null ? null : senderUser.getAddress());
1256                    kickPresence(presence, actorJID);
1257                }
1258            }
1259        }
1260        catch (NotAllowedException e) {
1261            // We should never receive this exception....in theory
1262
}
1263        return updatedPresences;
1264    }
1265
1266    public boolean isLocked() {
1267        return lockedTime > 0;
1268    }
1269
1270    public boolean isManuallyLocked() {
1271        return lockedTime > 0 && creationDate.getTime() != lockedTime;
1272    }
1273
1274    public void nicknameChanged(String JavaDoc oldNick, String JavaDoc newNick) {
1275        // Associate the existing MUCRole with the new nickname
1276
MUCRole occupant = occupants.get(oldNick.toLowerCase());
1277        // Check that we still have an occupant for the old nickname
1278
if (occupant != null) {
1279            occupants.put(newNick.toLowerCase(), occupant);
1280            // Remove the old nickname
1281
occupants.remove(oldNick.toLowerCase());
1282        }
1283    }
1284
1285    public void changeSubject(Message packet, MUCRole role) throws ForbiddenException {
1286        if ((canOccupantsChangeSubject() && role.getRole().compareTo(MUCRole.Role.visitor) < 0) ||
1287                MUCRole.Role.moderator == role.getRole()) {
1288            // Do nothing if the new subject is the same as the existing one
1289
if (packet.getSubject().equals(subject)) {
1290                return;
1291            }
1292            // Set the new subject to the room
1293
subject = packet.getSubject();
1294            MUCPersistenceManager.updateRoomSubject(this);
1295            // Notify all the occupants that the subject has changed
1296
packet.setFrom(role.getRoleAddress());
1297            send(packet);
1298        }
1299        else {
1300            throw new ForbiddenException();
1301        }
1302    }
1303
1304    public String JavaDoc getSubject() {
1305        return subject;
1306    }
1307
1308    public void setSubject(String JavaDoc subject) {
1309        this.subject = subject;
1310    }
1311
1312    public void sendInvitation(JID to, String JavaDoc reason, MUCRole senderRole, List JavaDoc<Element> extensions)
1313            throws ForbiddenException {
1314        if (!isMembersOnly() || canOccupantsInvite()
1315                || MUCRole.Affiliation.admin == senderRole.getAffiliation()
1316                || MUCRole.Affiliation.owner == senderRole.getAffiliation()) {
1317            // If the room is not members-only OR if the room is members-only and anyone can send
1318
// invitations or the sender is an admin or an owner, then send the invitation
1319
Message message = new Message();
1320            message.setFrom(role.getRoleAddress());
1321            message.setTo(to);
1322            // Add a list of extensions sent with the original message invitation (if any)
1323
if (extensions != null) {
1324                for(Element element : extensions) {
1325                    element.setParent(null);
1326                    message.getElement().add(element);
1327                }
1328            }
1329            Element frag = message.addChildElement("x", "http://jabber.org/protocol/muc#user");
1330            // ChatUser will be null if the room itself (ie. via admin console) made the request
1331
if (senderRole.getChatUser() != null) {
1332                frag.addElement("invite").addAttribute("from", senderRole.getChatUser().getAddress()
1333                        .toBareJID());
1334            }
1335            if (reason != null && reason.length() > 0) {
1336                Element invite = frag.element("invite");
1337                if (invite == null) {
1338                    invite.addElement("invite");
1339                }
1340                invite.addElement("reason").setText(reason);
1341            }
1342            if (isPasswordProtected()) {
1343                frag.addElement("password").setText(getPassword());
1344            }
1345
1346            // Include the jabber:x:conference information for backward compatibility
1347
frag = message.addChildElement("x", "jabber:x:conference");
1348            frag.addAttribute("jid", role.getRoleAddress().toBareJID());
1349
1350            // Send the message with the invitation
1351
router.route(message);
1352        }
1353        else {
1354            throw new ForbiddenException();
1355        }
1356    }
1357
1358    public void sendInvitationRejection(JID to, String JavaDoc reason, JID sender) {
1359        Message message = new Message();
1360        message.setFrom(role.getRoleAddress());
1361        message.setTo(to);
1362        Element frag = message.addChildElement("x", "http://jabber.org/protocol/muc#user");
1363        frag.addElement("decline").addAttribute("from", sender.toBareJID());
1364        if (reason != null && reason.length() > 0) {
1365            frag.element("decline").addElement("reason").setText(reason);
1366        }
1367
1368        // Send the message with the invitation
1369
router.route(message);
1370    }
1371
1372    public IQOwnerHandler getIQOwnerHandler() {
1373        return iqOwnerHandler;
1374    }
1375
1376    public IQAdminHandler getIQAdminHandler() {
1377        return iqAdminHandler;
1378    }
1379
1380    public MUCRoomHistory getRoomHistory() {
1381        return roomHistory;
1382    }
1383
1384    public Collection<String JavaDoc> getOwners() {
1385        return Collections.unmodifiableList(owners);
1386    }
1387
1388    public Collection<String JavaDoc> getAdmins() {
1389        return Collections.unmodifiableList(admins);
1390    }
1391
1392    public Collection<String JavaDoc> getMembers() {
1393        return Collections.unmodifiableMap(members).keySet();
1394    }
1395
1396    public Collection<String JavaDoc> getOutcasts() {
1397        return Collections.unmodifiableList(outcasts);
1398    }
1399
1400    public Collection<MUCRole> getModerators() {
1401        List JavaDoc<MUCRole> moderators = new ArrayList JavaDoc<MUCRole>();
1402        for (MUCRole role : occupants.values()) {
1403            if (MUCRole.Role.moderator == role.getRole()) {
1404                moderators.add(role);
1405            }
1406        }
1407        return moderators;
1408    }
1409
1410    public Collection<MUCRole> getParticipants() {
1411        List JavaDoc<MUCRole> participants = new ArrayList JavaDoc<MUCRole>();
1412        for (MUCRole role : occupants.values()) {
1413            if (MUCRole.Role.participant == role.getRole()) {
1414                participants.add(role);
1415            }
1416        }
1417        return participants;
1418    }
1419
1420    public Presence addModerator(JID jid, MUCRole senderRole) throws ForbiddenException {
1421        if (MUCRole.Affiliation.admin != senderRole.getAffiliation()
1422                && MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
1423            throw new ForbiddenException();
1424        }
1425        // Update the presence with the new role and inform all occupants
1426
try {
1427            return changeOccupantRole(jid, MUCRole.Role.moderator);
1428        }
1429        catch (NotAllowedException e) {
1430            // We should never receive this exception....in theory
1431
return null;
1432        }
1433    }
1434
1435    public Presence addParticipant(JID jid, String JavaDoc reason, MUCRole senderRole)
1436            throws NotAllowedException, ForbiddenException {
1437        if (MUCRole.Role.moderator != senderRole.getRole()) {
1438            throw new ForbiddenException();
1439        }
1440        // Update the presence with the new role and inform all occupants
1441
Presence updatedPresence = changeOccupantRole(jid, MUCRole.Role.participant);
1442        if (updatedPresence != null) {
1443            Element frag = updatedPresence.getChildElement(
1444                    "x", "http://jabber.org/protocol/muc#user");
1445            // Add the reason why the user was granted voice
1446
if (reason != null && reason.trim().length() > 0) {
1447                frag.element("item").addElement("reason").setText(reason);
1448            }
1449        }
1450        return updatedPresence;
1451    }
1452
1453    public Presence addVisitor(JID jid, MUCRole senderRole) throws NotAllowedException,
1454            ForbiddenException {
1455        if (MUCRole.Role.moderator != senderRole.getRole()) {
1456            throw new ForbiddenException();
1457        }
1458        return changeOccupantRole(jid, MUCRole.Role.visitor);
1459    }
1460
1461    public Presence kickOccupant(JID jid, JID actorJID, String JavaDoc reason)
1462            throws NotAllowedException {
1463        // Update the presence with the new role and inform all occupants
1464
Presence updatedPresence = changeOccupantRole(jid, MUCRole.Role.none);
1465        if (updatedPresence != null) {
1466            Element frag = updatedPresence.getChildElement(
1467                    "x", "http://jabber.org/protocol/muc#user");
1468
1469            // Add the status code 307 that indicates that the user was kicked
1470
frag.addElement("status").addAttribute("code", "307");
1471            // Add the reason why the user was kicked
1472
if (reason != null && reason.trim().length() > 0) {
1473                frag.element("item").addElement("reason").setText(reason);
1474            }
1475
1476            // Effectively kick the occupant from the room
1477
kickPresence(updatedPresence, actorJID);
1478        }
1479        return updatedPresence;
1480    }
1481
1482    /**
1483     * Kicks the occupant from the room. This means that the occupant will receive an unavailable
1484     * presence with the actor that initiated the kick (if any). The occupant will also be removed
1485     * from the occupants lists.
1486     *
1487     * @param kickPresence the presence of the occupant to kick from the room.
1488     * @param actorJID The JID of the actor that initiated the kick or <tt>null</tt> if the info
1489     * was not provided.
1490     */

1491    private void kickPresence(Presence kickPresence, JID actorJID) {
1492        MUCRole kickedRole;
1493        // Get the role to kick
1494
kickedRole = occupants.get(kickPresence.getFrom().getResource());
1495        if (kickedRole != null) {
1496            kickPresence = kickPresence.createCopy();
1497            // Add the actor's JID that kicked this user from the room
1498
if (actorJID != null && actorJID.toString().length() > 0) {
1499                Element frag = kickPresence.getChildElement(
1500                        "x", "http://jabber.org/protocol/muc#user");
1501                frag.element("item").addElement("actor").addAttribute("jid", actorJID.toBareJID());
1502            }
1503            // Send the unavailable presence to the banned user
1504
kickedRole.send(kickPresence);
1505            // Remove the occupant from the room's occupants lists
1506
removeOccupantRole(kickedRole);
1507        }
1508    }
1509
1510    public boolean canAnyoneDiscoverJID() {
1511        return canAnyoneDiscoverJID;
1512    }
1513
1514    public void setCanAnyoneDiscoverJID(boolean canAnyoneDiscoverJID) {
1515        this.canAnyoneDiscoverJID = canAnyoneDiscoverJID;
1516    }
1517
1518    public boolean canOccupantsChangeSubject() {
1519        return canOccupantsChangeSubject;
1520    }
1521
1522    public void setCanOccupantsChangeSubject(boolean canOccupantsChangeSubject) {
1523        this.canOccupantsChangeSubject = canOccupantsChangeSubject;
1524    }
1525
1526    public boolean canOccupantsInvite() {
1527        return canOccupantsInvite;
1528    }
1529
1530    public void setCanOccupantsInvite(boolean canOccupantsInvite) {
1531        this.canOccupantsInvite = canOccupantsInvite;
1532    }
1533
1534    public String JavaDoc getNaturalLanguageName() {
1535        return naturalLanguageName;
1536    }
1537
1538    public void setNaturalLanguageName(String JavaDoc naturalLanguageName) {
1539        this.naturalLanguageName = naturalLanguageName;
1540    }
1541
1542    public String JavaDoc getDescription() {
1543        return description;
1544    }
1545
1546    public void setDescription(String JavaDoc description) {
1547        this.description = description;
1548    }
1549
1550    public boolean isMembersOnly() {
1551        return membersOnly;
1552    }
1553
1554    public List JavaDoc<Presence> setMembersOnly(boolean membersOnly) {
1555        List JavaDoc<Presence> presences = new ArrayList JavaDoc<Presence>();
1556        if (membersOnly && !this.membersOnly) {
1557            // If the room was not members-only and now it is, kick occupants that aren't member
1558
// of the room
1559
for (MUCRole occupant : occupants.values()) {
1560                if (occupant.getAffiliation().compareTo(MUCRole.Affiliation.member) > 0) {
1561                    try {
1562                        presences.add(kickOccupant(occupant.getRoleAddress(), null,
1563                                LocaleUtils.getLocalizedString("muc.roomIsNowMembersOnly")));
1564                    }
1565                    catch (NotAllowedException e) {
1566                        Log.error(e);
1567                    }
1568                }
1569            }
1570        }
1571        this.membersOnly = membersOnly;
1572        return presences;
1573    }
1574
1575    public boolean isLogEnabled() {
1576        return logEnabled;
1577    }
1578
1579    public void setLogEnabled(boolean logEnabled) {
1580        this.logEnabled = logEnabled;
1581    }
1582
1583    public void setLoginRestrictedToNickname(boolean restricted) {
1584        this.loginRestrictedToNickname = restricted;
1585    }
1586
1587    public boolean isLoginRestrictedToNickname() {
1588        return loginRestrictedToNickname;
1589    }
1590
1591    public void setChangeNickname(boolean canChange) {
1592        this.canChangeNickname = canChange;
1593    }
1594
1595    public boolean canChangeNickname() {
1596        return canChangeNickname;
1597    }
1598
1599    public void setRegistrationEnabled(boolean registrationEnabled) {
1600        this.registrationEnabled = registrationEnabled;
1601    }
1602
1603    public boolean isRegistrationEnabled() {
1604        return registrationEnabled;
1605    }
1606
1607    public int getMaxUsers() {
1608        return maxUsers;
1609    }
1610
1611    public void setMaxUsers(int maxUsers) {
1612        this.maxUsers = maxUsers;
1613    }
1614
1615    public boolean isModerated() {
1616        return moderated;
1617    }
1618
1619    public void setModerated(boolean moderated) {
1620        this.moderated = moderated;
1621    }
1622
1623    public String JavaDoc getPassword() {
1624        return password;
1625    }
1626
1627    public void setPassword(String JavaDoc password) {
1628        this.password = password;
1629    }
1630
1631    public boolean isPasswordProtected() {
1632        return password != null && password.trim().length() > 0;
1633    }
1634
1635    public boolean isPersistent() {
1636        return persistent;
1637    }
1638
1639    public boolean wasSavedToDB() {
1640        if (!isPersistent()) {
1641            return false;
1642        }
1643        return savedToDB;
1644    }
1645    
1646    public void setSavedToDB(boolean saved) {
1647        this.savedToDB = saved;
1648    }
1649    
1650    public void setPersistent(boolean persistent) {
1651        this.persistent = persistent;
1652    }
1653
1654    public boolean isPublicRoom() {
1655        return !isDestroyed && publicRoom;
1656    }
1657
1658    public void setPublicRoom(boolean publicRoom) {
1659        this.publicRoom = publicRoom;
1660    }
1661
1662    public List JavaDoc<String JavaDoc> getRolesToBroadcastPresence() {
1663        return Collections.unmodifiableList(rolesToBroadcastPresence);
1664    }
1665
1666    public void setRolesToBroadcastPresence(List JavaDoc<String JavaDoc> rolesToBroadcastPresence) {
1667        // TODO If the list changes while there are occupants in the room we must send available or
1668
// unavailable presences of the affected occupants to the rest of the occupants
1669
this.rolesToBroadcastPresence = rolesToBroadcastPresence;
1670    }
1671
1672    /**
1673     * Returns true if we need to check whether a presence could be sent or not.
1674     *
1675     * @return true if we need to check whether a presence could be sent or not.
1676     */

1677    private boolean hasToCheckRoleToBroadcastPresence() {
1678        // For performance reasons the check is done based on the size of the collection.
1679
return rolesToBroadcastPresence.size() < 3;
1680    }
1681
1682    public boolean canBroadcastPresence(String JavaDoc roleToBroadcast) {
1683        return "none".equals(roleToBroadcast) || rolesToBroadcastPresence.contains(roleToBroadcast);
1684    }
1685
1686    public void lock(MUCRole senderRole) throws ForbiddenException {
1687        if (MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
1688            throw new ForbiddenException();
1689        }
1690        if (isLocked()) {
1691            // Do nothing if the room was already locked
1692
return;
1693        }
1694        setLocked(true);
1695        if (senderRole.getChatUser() != null) {
1696            // Send to the occupant that locked the room a message saying so
1697
Message message = new Message();
1698            message.setType(Message.Type.groupchat);
1699            message.setBody(LocaleUtils.getLocalizedString("muc.locked"));
1700            message.setFrom(getRole().getRoleAddress());
1701            senderRole.send(message);
1702        }
1703    }
1704
1705    public void unlock(MUCRole senderRole) throws ForbiddenException {
1706        if (MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
1707            throw new ForbiddenException();
1708        }
1709        if (!isLocked()) {
1710            // Do nothing if the room was already unlocked
1711
return;
1712        }
1713        setLocked(false);
1714        if (senderRole.getChatUser() != null) {
1715            // Send to the occupant that unlocked the room a message saying so
1716
Message message = new Message();
1717            message.setType(Message.Type.groupchat);
1718            message.setBody(LocaleUtils.getLocalizedString("muc.unlocked"));
1719            message.setFrom(getRole().getRoleAddress());
1720            senderRole.send(message);
1721        }
1722    }
1723
1724    private void setLocked(boolean locked) {
1725        if (locked) {
1726            this.lockedTime = System.currentTimeMillis();
1727        }
1728        else {
1729            this.lockedTime = 0;
1730        }
1731        MUCPersistenceManager.updateRoomLock(this);
1732    }
1733
1734    /**
1735     * Sets the date when the room was locked. Initially when the room is created it is locked so
1736     * the locked date is the creation date of the room. Afterwards, the room may be manually
1737     * locked and unlocked so the locked date may be in these cases different than the creation
1738     * date. A Date with time 0 means that the the room is unlocked.
1739     *
1740     * @param lockedTime the date when the room was locked.
1741     */

1742    void setLockedDate(Date lockedTime) {
1743        this.lockedTime = lockedTime.getTime();
1744    }
1745
1746    /**
1747     * Returns the date when the room was locked. Initially when the room is created it is locked so
1748     * the locked date is the creation date of the room. Afterwards, the room may be manually
1749     * locked and unlocked so the locked date may be in these cases different than the creation
1750     * date. When the room is unlocked a Date with time 0 is returned.
1751     *
1752     * @return the date when the room was locked.
1753     */

1754    Date getLockedDate() {
1755        return new Date(lockedTime);
1756    }
1757
1758    public List JavaDoc<Presence> addAdmins(List JavaDoc<String JavaDoc> newAdmins, MUCRole senderRole)
1759            throws ForbiddenException, ConflictException {
1760        List JavaDoc<Presence> answer = new ArrayList JavaDoc<Presence>(newAdmins.size());
1761        for (String JavaDoc newAdmin : newAdmins) {
1762            if (newAdmin.trim().length() > 0 && !admins.contains(newAdmin)) {
1763                answer.addAll(addAdmin(newAdmin, senderRole));
1764            }
1765        }
1766        return answer;
1767    }
1768
1769    public List JavaDoc<Presence> addOwners(List JavaDoc<String JavaDoc> newOwners, MUCRole senderRole)
1770            throws ForbiddenException {
1771        List JavaDoc<Presence> answer = new ArrayList JavaDoc<Presence>(newOwners.size());
1772        for (String JavaDoc newOwner : newOwners) {
1773            if (newOwner.trim().length() > 0 && !owners.contains(newOwner)) {
1774                answer.addAll(addOwner(newOwner, senderRole));
1775            }
1776        }
1777        return answer;
1778    }
1779
1780    public void saveToDB() {
1781        // Make the room persistent
1782
MUCPersistenceManager.saveToDB(this);
1783        if (!savedToDB) {
1784            // Set that the room is now in the DB
1785
savedToDB = true;
1786            // Save the existing room owners to the DB
1787
for (String JavaDoc owner : owners) {
1788                MUCPersistenceManager.saveAffiliationToDB(
1789                    this,
1790                    owner,
1791                    null,
1792                    MUCRole.Affiliation.owner,
1793                    MUCRole.Affiliation.none);
1794            }
1795            // Save the existing room admins to the DB
1796
for (String JavaDoc admin : admins) {
1797                MUCPersistenceManager.saveAffiliationToDB(
1798                    this,
1799                    admin,
1800                    null,
1801                    MUCRole.Affiliation.admin,
1802                    MUCRole.Affiliation.none);
1803            }
1804            // Save the existing room members to the DB
1805
for (Iterator it=members.keySet().iterator(); it.hasNext();) {
1806                String JavaDoc bareJID = (String JavaDoc)it.next();
1807                MUCPersistenceManager.saveAffiliationToDB(this, bareJID, (String JavaDoc) members
1808                        .get(bareJID), MUCRole.Affiliation.member, MUCRole.Affiliation.none);
1809            }
1810            // Save the existing room outcasts to the DB
1811
for (String JavaDoc outcast : outcasts) {
1812                MUCPersistenceManager.saveAffiliationToDB(
1813                    this,
1814                    outcast,
1815                    null,
1816                    MUCRole.Affiliation.outcast,
1817                    MUCRole.Affiliation.none);
1818            }
1819        }
1820    }
1821}
Popular Tags