0001: /**
0002: * $RCSfile$
0003: * $Revision: 3158 $
0004: * $Date: 2005-12-04 22:55:49 -0300 (Sun, 04 Dec 2005) $
0005: *
0006: * Copyright (C) 2004 Jive Software. All rights reserved.
0007: *
0008: * This software is published under the terms of the GNU Public License (GPL),
0009: * a copy of which is included in this distribution.
0010: */package org.jivesoftware.openfire.muc.spi;
0011:
0012: import org.dom4j.Element;
0013: import org.jivesoftware.database.SequenceManager;
0014: import org.jivesoftware.openfire.PacketRouter;
0015: import org.jivesoftware.openfire.XMPPServer;
0016: import org.jivesoftware.openfire.auth.UnauthorizedException;
0017: import org.jivesoftware.openfire.cluster.NodeID;
0018: import org.jivesoftware.openfire.muc.*;
0019: import org.jivesoftware.openfire.muc.cluster.*;
0020: import org.jivesoftware.openfire.user.UserAlreadyExistsException;
0021: import org.jivesoftware.openfire.user.UserNotFoundException;
0022: import org.jivesoftware.util.*;
0023: import org.jivesoftware.util.cache.CacheFactory;
0024: import org.jivesoftware.util.cache.ExternalizableUtil;
0025: import org.xmpp.packet.*;
0026:
0027: import java.io.IOException;
0028: import java.io.ObjectInput;
0029: import java.io.ObjectOutput;
0030: import java.util.*;
0031: import java.util.concurrent.ConcurrentHashMap;
0032: import java.util.concurrent.CopyOnWriteArrayList;
0033: import java.util.concurrent.locks.ReadWriteLock;
0034: import java.util.concurrent.locks.ReentrantReadWriteLock;
0035:
0036: /**
0037: * Implementation of a chatroom that is being hosted by this JVM. A LocalMUCRoom could represent
0038: * a persistent room which means that its configuration will be maintained in synch with its
0039: * representation in the database.<p>
0040: *
0041: * When running in a cluster each cluster node will have its own copy of local rooms. Persistent
0042: * rooms will be loaded by each cluster node when starting up. Not persistent rooms will be copied
0043: * from the senior cluster member. All room occupants will be copied from the senior cluster member
0044: * too.
0045: *
0046: * @author Gaston Dombiak
0047: */
0048: public class LocalMUCRoom implements MUCRoom {
0049:
0050: /**
0051: * The server hosting the room.
0052: */
0053: private MultiUserChatServerImpl server;
0054:
0055: /**
0056: * The occupants of the room accessible by the occupants nickname.
0057: */
0058: private Map<String, MUCRole> occupants = new ConcurrentHashMap<String, MUCRole>();
0059:
0060: /**
0061: * The occupants of the room accessible by the occupants bare JID.
0062: */
0063: private Map<String, List<MUCRole>> occupantsByBareJID = new ConcurrentHashMap<String, List<MUCRole>>();
0064:
0065: /**
0066: * The occupants of the room accessible by the occupants full JID.
0067: */
0068: private Map<JID, MUCRole> occupantsByFullJID = new ConcurrentHashMap<JID, MUCRole>();
0069:
0070: /**
0071: * The name of the room.
0072: */
0073: private String name;
0074:
0075: /**
0076: * A lock to protect the room occupants.
0077: */
0078: ReadWriteLock lock = new ReentrantReadWriteLock();
0079:
0080: /**
0081: * The role of the room itself.
0082: */
0083: private MUCRole role = new RoomRole(this );
0084:
0085: /**
0086: * The router used to send packets for the room.
0087: */
0088: private PacketRouter router;
0089:
0090: /**
0091: * The start time of the chat.
0092: */
0093: long startTime;
0094:
0095: /**
0096: * The end time of the chat.
0097: */
0098: long endTime;
0099:
0100: /**
0101: * After a room has been destroyed it may remain in memory but it won't be possible to use it.
0102: * When a room is destroyed it is immediately removed from the MultiUserChatServer but it's
0103: * possible that while the room was being destroyed it was being used by another thread so we
0104: * need to protect the room under these rare circumstances.
0105: */
0106: boolean isDestroyed = false;
0107:
0108: /**
0109: * ChatRoomHistory object.
0110: */
0111: private MUCRoomHistory roomHistory;
0112:
0113: /**
0114: * Time when the room was locked. A value of zero means that the room is unlocked.
0115: */
0116: private long lockedTime;
0117:
0118: /**
0119: * List of chatroom's owner. The list contains only bare jid.
0120: */
0121: List<String> owners = new CopyOnWriteArrayList<String>();
0122:
0123: /**
0124: * List of chatroom's admin. The list contains only bare jid.
0125: */
0126: List<String> admins = new CopyOnWriteArrayList<String>();
0127:
0128: /**
0129: * List of chatroom's members. The list contains only bare jid.
0130: */
0131: private Map<String, String> members = new ConcurrentHashMap<String, String>();
0132:
0133: /**
0134: * List of chatroom's outcast. The list contains only bare jid of not allowed users.
0135: */
0136: private List<String> outcasts = new CopyOnWriteArrayList<String>();
0137:
0138: /**
0139: * The natural language name of the room.
0140: */
0141: private String naturalLanguageName;
0142:
0143: /**
0144: * Description of the room. The owner can change the description using the room configuration
0145: * form.
0146: */
0147: private String description;
0148:
0149: /**
0150: * Indicates if occupants are allowed to change the subject of the room.
0151: */
0152: private boolean canOccupantsChangeSubject = JiveGlobals
0153: .getBooleanProperty("muc.room.canOccupantsChangeSubject",
0154: false);
0155:
0156: /**
0157: * Maximum number of occupants that could be present in the room. If the limit's been reached
0158: * and a user tries to join, a not-allowed error will be returned.
0159: */
0160: private int maxUsers = 30;
0161:
0162: /**
0163: * List of roles of which presence will be broadcasted to the rest of the occupants. This
0164: * feature is useful for implementing "invisible" occupants.
0165: */
0166: private List<String> rolesToBroadcastPresence = new ArrayList<String>();
0167:
0168: /**
0169: * A public room means that the room is searchable and visible. This means that the room can be
0170: * located using disco requests.
0171: */
0172: private boolean publicRoom = JiveGlobals.getBooleanProperty(
0173: "muc.room.publicRoom", true);
0174:
0175: /**
0176: * Persistent rooms are saved to the database to make sure that rooms configurations can be
0177: * restored in case the server goes down.
0178: */
0179: private boolean persistent = JiveGlobals.getBooleanProperty(
0180: "muc.room.persistent", false);
0181:
0182: /**
0183: * Moderated rooms enable only participants to speak. Users that join the room and aren't
0184: * participants can't speak (they are just visitors).
0185: */
0186: private boolean moderated = JiveGlobals.getBooleanProperty(
0187: "muc.room.moderated", false);
0188:
0189: /**
0190: * A room is considered members-only if an invitation is required in order to enter the room.
0191: * Any user that is not a member of the room won't be able to join the room unless the user
0192: * decides to register with the room (thus becoming a member).
0193: */
0194: private boolean membersOnly = JiveGlobals.getBooleanProperty(
0195: "muc.room.membersOnly", false);
0196:
0197: /**
0198: * Some rooms may restrict the occupants that are able to send invitations. Sending an
0199: * invitation in a members-only room adds the invitee to the members list.
0200: */
0201: private boolean canOccupantsInvite = JiveGlobals
0202: .getBooleanProperty("muc.room.canOccupantsInvite", false);
0203:
0204: /**
0205: * The password that every occupant should provide in order to enter the room.
0206: */
0207: private String password = null;
0208:
0209: /**
0210: * Every presence packet can include the JID of every occupant unless the owner deactives this
0211: * configuration.
0212: */
0213: private boolean canAnyoneDiscoverJID = JiveGlobals
0214: .getBooleanProperty("muc.room.canAnyoneDiscoverJID", true);
0215:
0216: /**
0217: * Enables the logging of the conversation. The conversation in the room will be saved to the
0218: * database.
0219: */
0220: private boolean logEnabled = JiveGlobals.getBooleanProperty(
0221: "muc.room.logEnabled", false);
0222:
0223: /**
0224: * Enables the logging of the conversation. The conversation in the room will be saved to the
0225: * database.
0226: */
0227: private boolean loginRestrictedToNickname = JiveGlobals
0228: .getBooleanProperty("muc.room.loginRestrictedToNickname",
0229: false);
0230:
0231: /**
0232: * Enables the logging of the conversation. The conversation in the room will be saved to the
0233: * database.
0234: */
0235: private boolean canChangeNickname = JiveGlobals.getBooleanProperty(
0236: "muc.room.canChangeNickname", true);
0237:
0238: /**
0239: * Enables the logging of the conversation. The conversation in the room will be saved to the
0240: * database.
0241: */
0242: private boolean registrationEnabled = JiveGlobals
0243: .getBooleanProperty("muc.room.registrationEnabled", true);
0244:
0245: /**
0246: * Internal component that handles IQ packets sent by the room owners.
0247: */
0248: private IQOwnerHandler iqOwnerHandler;
0249:
0250: /**
0251: * Internal component that handles IQ packets sent by moderators, admins and owners.
0252: */
0253: private IQAdminHandler iqAdminHandler;
0254:
0255: /**
0256: * The last known subject of the room. This information is used to respond disco requests. The
0257: * MUCRoomHistory class holds the history of the room together with the last message that set
0258: * the room's subject.
0259: */
0260: private String subject = "";
0261:
0262: /**
0263: * The ID of the room. If the room is temporary and does not log its conversation then the value
0264: * will always be -1. Otherwise a value will be obtained from the database.
0265: */
0266: private long roomID = -1;
0267:
0268: /**
0269: * The date when the room was created.
0270: */
0271: private Date creationDate;
0272:
0273: /**
0274: * The last date when the room's configuration was modified.
0275: */
0276: private Date modificationDate;
0277:
0278: /**
0279: * The date when the last occupant left the room. A null value means that there are occupants
0280: * in the room at the moment.
0281: */
0282: private Date emptyDate;
0283:
0284: /**
0285: * Indicates if the room is present in the database.
0286: */
0287: private boolean savedToDB = false;
0288:
0289: /**
0290: * Do not use this constructor. It was added to implement the Externalizable
0291: * interface required to work inside of a cluster.
0292: */
0293: public LocalMUCRoom() {
0294: }
0295:
0296: /**
0297: * Create a new chat room.
0298: *
0299: * @param chatserver the server hosting the room.
0300: * @param roomname the name of the room.
0301: * @param packetRouter the router for sending packets from the room.
0302: */
0303: LocalMUCRoom(MultiUserChatServer chatserver, String roomname,
0304: PacketRouter packetRouter) {
0305: this .server = (MultiUserChatServerImpl) chatserver;
0306: this .name = roomname;
0307: this .naturalLanguageName = roomname;
0308: this .description = roomname;
0309: this .router = packetRouter;
0310: this .startTime = System.currentTimeMillis();
0311: this .creationDate = new Date(startTime);
0312: this .modificationDate = new Date(startTime);
0313: this .emptyDate = new Date(startTime);
0314: // TODO Allow to set the history strategy from the configuration form?
0315: roomHistory = new MUCRoomHistory(this , new HistoryStrategy(
0316: server.getHistoryStrategy()));
0317: this .iqOwnerHandler = new IQOwnerHandler(this , packetRouter);
0318: this .iqAdminHandler = new IQAdminHandler(this , packetRouter);
0319: // No one can join the room except the room's owner
0320: this .lockedTime = startTime;
0321: // Set the default roles for which presence is broadcast
0322: rolesToBroadcastPresence.add("moderator");
0323: rolesToBroadcastPresence.add("participant");
0324: rolesToBroadcastPresence.add("visitor");
0325: }
0326:
0327: public String getName() {
0328: return name;
0329: }
0330:
0331: public long getID() {
0332: if (isPersistent() || isLogEnabled()) {
0333: if (roomID == -1) {
0334: roomID = SequenceManager.nextID(JiveConstants.MUC_ROOM);
0335: }
0336: }
0337: return roomID;
0338: }
0339:
0340: public void setID(long roomID) {
0341: this .roomID = roomID;
0342: }
0343:
0344: public Date getCreationDate() {
0345: return creationDate;
0346: }
0347:
0348: public void setCreationDate(Date creationDate) {
0349: this .creationDate = creationDate;
0350: }
0351:
0352: public Date getModificationDate() {
0353: return modificationDate;
0354: }
0355:
0356: public void setModificationDate(Date modificationDate) {
0357: this .modificationDate = modificationDate;
0358: }
0359:
0360: public void setEmptyDate(Date emptyDate) {
0361: // Do nothing if old value is same as new value
0362: if (this .emptyDate == emptyDate) {
0363: return;
0364: }
0365: this .emptyDate = emptyDate;
0366: MUCPersistenceManager.updateRoomEmptyDate(this );
0367: }
0368:
0369: public Date getEmptyDate() {
0370: return this .emptyDate;
0371: }
0372:
0373: public MUCRole getRole() {
0374: return role;
0375: }
0376:
0377: public MUCRole getOccupant(String nickname)
0378: throws UserNotFoundException {
0379: if (nickname == null) {
0380: throw new UserNotFoundException();
0381: }
0382: MUCRole role = occupants.get(nickname.toLowerCase());
0383: if (role != null) {
0384: return role;
0385: }
0386: throw new UserNotFoundException();
0387: }
0388:
0389: public List<MUCRole> getOccupantsByBareJID(String jid)
0390: throws UserNotFoundException {
0391: List<MUCRole> roles = occupantsByBareJID.get(jid);
0392: if (roles != null && !roles.isEmpty()) {
0393: return Collections.unmodifiableList(roles);
0394: }
0395: throw new UserNotFoundException();
0396: }
0397:
0398: public MUCRole getOccupantByFullJID(JID jid) {
0399: MUCRole role = occupantsByFullJID.get(jid);
0400: if (role != null) {
0401: return role;
0402: }
0403: return null;
0404: }
0405:
0406: public Collection<MUCRole> getOccupants() {
0407: return Collections.unmodifiableCollection(occupants.values());
0408: }
0409:
0410: public int getOccupantsCount() {
0411: return occupants.size();
0412: }
0413:
0414: public boolean hasOccupant(String nickname) {
0415: return occupants.containsKey(nickname.toLowerCase());
0416: }
0417:
0418: public String getReservedNickname(String bareJID) {
0419: String answer = members.get(bareJID);
0420: if (answer == null || answer.trim().length() == 0) {
0421: return null;
0422: }
0423: return answer;
0424: }
0425:
0426: public MUCRole.Affiliation getAffiliation(String bareJID) {
0427: if (owners.contains(bareJID)) {
0428: return MUCRole.Affiliation.owner;
0429: } else if (admins.contains(bareJID)) {
0430: return MUCRole.Affiliation.admin;
0431: } else if (members.containsKey(bareJID)) {
0432: return MUCRole.Affiliation.member;
0433: } else if (outcasts.contains(bareJID)) {
0434: return MUCRole.Affiliation.outcast;
0435: }
0436: return MUCRole.Affiliation.none;
0437: }
0438:
0439: public LocalMUCRole joinRoom(String nickname, String password,
0440: HistoryRequest historyRequest, LocalMUCUser user,
0441: Presence presence) throws UnauthorizedException,
0442: UserAlreadyExistsException, RoomLockedException,
0443: ForbiddenException, RegistrationRequiredException,
0444: ConflictException, ServiceUnavailableException,
0445: NotAcceptableException {
0446: LocalMUCRole joinRole = null;
0447: lock.writeLock().lock();
0448: try {
0449: // If the room has a limit of max user then check if the limit has been reached
0450: if (isDestroyed
0451: || (getMaxUsers() > 0 && getOccupantsCount() >= getMaxUsers())) {
0452: throw new ServiceUnavailableException();
0453: }
0454: boolean isOwner = owners.contains(user.getAddress()
0455: .toBareJID());
0456: // If the room is locked and this user is not an owner raise a RoomLocked exception
0457: if (isLocked()) {
0458: if (!isOwner) {
0459: throw new RoomLockedException();
0460: }
0461: }
0462: // If the user is already in the room raise a UserAlreadyExists exception
0463: if (occupants.containsKey(nickname.toLowerCase())) {
0464: throw new UserAlreadyExistsException();
0465: }
0466: // If the room is password protected and the provided password is incorrect raise a
0467: // Unauthorized exception
0468: if (isPasswordProtected()) {
0469: if (password == null || !password.equals(getPassword())) {
0470: throw new UnauthorizedException();
0471: }
0472: }
0473: // If another user attempts to join the room with a nickname reserved by the first user
0474: // raise a ConflictException
0475: if (members.containsValue(nickname)) {
0476: if (!nickname.equals(members.get(user.getAddress()
0477: .toBareJID()))) {
0478: throw new ConflictException();
0479: }
0480: }
0481: if (isLoginRestrictedToNickname()) {
0482: String reservedNickname = members.get(user.getAddress()
0483: .toBareJID());
0484: if (reservedNickname != null
0485: && !nickname.equals(reservedNickname)) {
0486: throw new NotAcceptableException();
0487: }
0488: }
0489:
0490: // Set the corresponding role based on the user's affiliation
0491: MUCRole.Role role;
0492: MUCRole.Affiliation affiliation;
0493: if (isOwner) {
0494: // The user is an owner. Set the role and affiliation accordingly.
0495: role = MUCRole.Role.moderator;
0496: affiliation = MUCRole.Affiliation.owner;
0497: } else if (server.getSysadmins().contains(
0498: user.getAddress().toBareJID())) {
0499: // The user is a system administrator of the MUC service. Treat him as an owner
0500: // although he won't appear in the list of owners
0501: role = MUCRole.Role.moderator;
0502: affiliation = MUCRole.Affiliation.owner;
0503: } else if (admins.contains(user.getAddress().toBareJID())) {
0504: // The user is an admin. Set the role and affiliation accordingly.
0505: role = MUCRole.Role.moderator;
0506: affiliation = MUCRole.Affiliation.admin;
0507: } else if (members.containsKey(user.getAddress()
0508: .toBareJID())) {
0509: // The user is a member. Set the role and affiliation accordingly.
0510: role = MUCRole.Role.participant;
0511: affiliation = MUCRole.Affiliation.member;
0512: } else if (outcasts.contains(user.getAddress().toBareJID())) {
0513: // The user is an outcast. Raise a "Forbidden" exception.
0514: throw new ForbiddenException();
0515: } else {
0516: // The user has no affiliation (i.e. NONE). Set the role accordingly.
0517: if (isMembersOnly()) {
0518: // The room is members-only and the user is not a member. Raise a
0519: // "Registration Required" exception.
0520: throw new RegistrationRequiredException();
0521: }
0522: role = (isModerated() ? MUCRole.Role.visitor
0523: : MUCRole.Role.participant);
0524: affiliation = MUCRole.Affiliation.none;
0525: }
0526: // Create a new role for this user in this room
0527: joinRole = new LocalMUCRole(server, this , nickname, role,
0528: affiliation, user, presence, router);
0529: // Add the new user as an occupant of this room
0530: occupants.put(nickname.toLowerCase(), joinRole);
0531: // Update the tables of occupants based on the bare and full JID
0532: List<MUCRole> list = occupantsByBareJID.get(user
0533: .getAddress().toBareJID());
0534: if (list == null) {
0535: list = new ArrayList<MUCRole>();
0536: occupantsByBareJID.put(user.getAddress().toBareJID(),
0537: list);
0538: }
0539: list.add(joinRole);
0540: occupantsByFullJID.put(user.getAddress(), joinRole);
0541: } finally {
0542: lock.writeLock().unlock();
0543: }
0544: // Notify other cluster nodes that a new occupant joined the room
0545: CacheFactory.doClusterTask(new OccupantAddedEvent(this ,
0546: joinRole));
0547:
0548: // Send presence of existing occupants to new occupant
0549: sendInitialPresences(joinRole);
0550: // It is assumed that the room is new based on the fact that it's locked and
0551: // that it was locked when it was created.
0552: boolean isRoomNew = isLocked()
0553: && creationDate.getTime() == lockedTime;
0554: try {
0555: // Send the presence of this new occupant to existing occupants
0556: Presence joinPresence = joinRole.getPresence().createCopy();
0557: if (isRoomNew) {
0558: Element frag = joinPresence.getChildElement("x",
0559: "http://jabber.org/protocol/muc#user");
0560: frag.addElement("status").addAttribute("code", "201");
0561: }
0562: broadcastPresence(joinPresence);
0563: } catch (Exception e) {
0564: Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
0565: }
0566: // If the room has just been created send the "room locked until configuration is
0567: // confirmed" message
0568: if (isRoomNew) {
0569: Message message = new Message();
0570: message.setType(Message.Type.groupchat);
0571: message.setBody(LocaleUtils.getLocalizedString("muc.new"));
0572: message.setFrom(role.getRoleAddress());
0573: joinRole.send(message);
0574: } else if (isLocked()) {
0575: // Warn the owner that the room is locked but it's not new
0576: Message message = new Message();
0577: message.setType(Message.Type.groupchat);
0578: message.setBody(LocaleUtils
0579: .getLocalizedString("muc.locked"));
0580: message.setFrom(role.getRoleAddress());
0581: joinRole.send(message);
0582: } else if (canAnyoneDiscoverJID()) {
0583: // Warn the new occupant that the room is non-anonymous (i.e. his JID will be
0584: // public)
0585: Message message = new Message();
0586: message.setType(Message.Type.groupchat);
0587: message.setBody(LocaleUtils
0588: .getLocalizedString("muc.warnnonanonymous"));
0589: message.setFrom(role.getRoleAddress());
0590: Element frag = message.addChildElement("x",
0591: "http://jabber.org/protocol/muc#user");
0592: frag.addElement("status").addAttribute("code", "100");
0593: joinRole.send(message);
0594: }
0595: if (historyRequest == null) {
0596: Iterator history = roomHistory.getMessageHistory();
0597: while (history.hasNext()) {
0598: joinRole.send((Message) history.next());
0599: }
0600: } else {
0601: historyRequest.sendHistory(joinRole, roomHistory);
0602: }
0603: // Update the date when the last occupant left the room
0604: setEmptyDate(null);
0605: // Fire event that occupant joined the room
0606: server.fireOccupantJoined(getRole().getRoleAddress(), user
0607: .getAddress(), joinRole.getNickname());
0608: return joinRole;
0609: }
0610:
0611: /**
0612: * Sends presence of existing occupants to new occupant.
0613: *
0614: * @param joinRole the role of the new occupant in the room.
0615: */
0616: private void sendInitialPresences(LocalMUCRole joinRole) {
0617: for (MUCRole occupant : occupants.values()) {
0618: if (occupant == joinRole) {
0619: continue;
0620: }
0621: Presence occupantPresence = occupant.getPresence();
0622: // Skip to the next occupant if we cannot send presence of this occupant
0623: if (hasToCheckRoleToBroadcastPresence()) {
0624: Element frag = occupantPresence.getChildElement("x",
0625: "http://jabber.org/protocol/muc#user");
0626: // Check if we can broadcast the presence for this role
0627: if (!canBroadcastPresence(frag.element("item")
0628: .attributeValue("role"))) {
0629: continue;
0630: }
0631: }
0632: // Don't include the occupant's JID if the room is semi-anon and the new occupant
0633: // is not a moderator
0634: if (!canAnyoneDiscoverJID()
0635: && MUCRole.Role.moderator != joinRole.getRole()) {
0636: occupantPresence = occupantPresence.createCopy();
0637: Element frag = occupantPresence.getChildElement("x",
0638: "http://jabber.org/protocol/muc#user");
0639: frag.element("item").addAttribute("jid", null);
0640: }
0641: joinRole.send(occupantPresence);
0642: }
0643: }
0644:
0645: public void occupantAdded(OccupantAddedEvent event) {
0646: // Do not add new occupant with one with same nickname already exists
0647: if (occupants.containsKey(event.getNickname().toLowerCase())) {
0648: // TODO Handle conflict of nicknames
0649: return;
0650: }
0651: // Create a proxy for the occupant that joined the room from another cluster node
0652: RemoteMUCRole joinRole = new RemoteMUCRole(server, event);
0653: // Add the new user as an occupant of this room
0654: occupants.put(event.getNickname().toLowerCase(), joinRole);
0655: // Update the tables of occupants based on the bare and full JID
0656: List<MUCRole> list = occupantsByBareJID.get(event
0657: .getUserAddress().toBareJID());
0658: if (list == null) {
0659: list = new ArrayList<MUCRole>();
0660: occupantsByBareJID.put(event.getUserAddress().toBareJID(),
0661: list);
0662: }
0663: list.add(joinRole);
0664: occupantsByFullJID.put(event.getUserAddress(), joinRole);
0665:
0666: // Update the date when the last occupant left the room
0667: setEmptyDate(null);
0668: // Fire event that occupant joined the room
0669: server.fireOccupantJoined(getRole().getRoleAddress(), event
0670: .getUserAddress(), joinRole.getNickname());
0671: // Check if we need to send presences of the new occupant to occupants hosted by this JVM
0672: if (event.isSendPresence()) {
0673: for (MUCRole occupant : occupants.values()) {
0674: if (occupant.isLocal()) {
0675: occupant.send(event.getPresence().createCopy());
0676: }
0677: }
0678: }
0679: }
0680:
0681: public void leaveRoom(MUCRole leaveRole) {
0682: if (leaveRole.isLocal()) {
0683: // Ask other cluster nodes to remove occupant from room
0684: OccupantLeftEvent event = new OccupantLeftEvent(this ,
0685: leaveRole);
0686: CacheFactory.doClusterTask(event);
0687: }
0688:
0689: try {
0690: Presence presence = leaveRole.getPresence().createCopy();
0691: presence.setType(Presence.Type.unavailable);
0692: presence.setStatus(null);
0693: // Change (or add) presence information about roles and affiliations
0694: Element childElement = presence.getChildElement("x",
0695: "http://jabber.org/protocol/muc#user");
0696: if (childElement == null) {
0697: childElement = presence.addChildElement("x",
0698: "http://jabber.org/protocol/muc#user");
0699: }
0700: Element item = childElement.element("item");
0701: if (item == null) {
0702: item = childElement.addElement("item");
0703: }
0704: item.addAttribute("role", "none");
0705: // Inform the leaving user that he/she has left the room
0706: leaveRole.send(presence);
0707: // Inform the rest of the room occupants that the user has left the room
0708: broadcastPresence(presence);
0709: } catch (Exception e) {
0710: Log.error(e);
0711: }
0712:
0713: // Remove occupant from room and destroy room if empty and not persistent
0714: OccupantLeftEvent event = new OccupantLeftEvent(this , leaveRole);
0715: event.setOriginator(true);
0716: event.run();
0717: }
0718:
0719: public void leaveRoom(OccupantLeftEvent event) {
0720: MUCRole leaveRole = event.getRole();
0721: if (leaveRole == null) {
0722: return;
0723: }
0724: lock.writeLock().lock();
0725: try {
0726: // Removes the role from the room
0727: removeOccupantRole(leaveRole);
0728:
0729: // TODO Implement this: If the room owner becomes unavailable for any reason before
0730: // submitting the form (e.g., a lost connection), the service will receive a presence
0731: // stanza of type "unavailable" from the owner to the room@service/nick or room@service
0732: // (or both). The service MUST then destroy the room, sending a presence stanza of type
0733: // "unavailable" from the room to the owner including a <destroy/> element and reason
0734: // (if provided) as defined under the "Destroying a Room" use case.
0735:
0736: // Remove the room from the server only if there are no more occupants and the room is
0737: // not persistent
0738: if (occupants.isEmpty() && !isPersistent()) {
0739: endTime = System.currentTimeMillis();
0740: if (event.isOriginator()) {
0741: server.removeChatRoom(name);
0742: }
0743: // Fire event that the room has been destroyed
0744: server.fireRoomDestroyed(getRole().getRoleAddress());
0745: }
0746: if (occupants.isEmpty()) {
0747: // Update the date when the last occupant left the room
0748: setEmptyDate(new Date());
0749: }
0750: } finally {
0751: lock.writeLock().unlock();
0752: }
0753: }
0754:
0755: /**
0756: * Removes the role of the occupant from all the internal occupants collections. The role will
0757: * also be removed from the user's roles.
0758: *
0759: * @param leaveRole the role to remove.
0760: */
0761: private void removeOccupantRole(MUCRole leaveRole) {
0762: occupants.remove(leaveRole.getNickname().toLowerCase());
0763:
0764: JID userAddress = leaveRole.getUserAddress();
0765: // Notify the user that he/she is no longer in the room
0766: leaveRole.destroy();
0767: // Update the tables of occupants based on the bare and full JID
0768: List list = occupantsByBareJID.get(userAddress.toBareJID());
0769: if (list != null) {
0770: list.remove(leaveRole);
0771: if (list.isEmpty()) {
0772: occupantsByBareJID.remove(userAddress.toBareJID());
0773: }
0774: }
0775: occupantsByFullJID.remove(userAddress);
0776: // Fire event that occupant left the room
0777: server
0778: .fireOccupantLeft(getRole().getRoleAddress(),
0779: userAddress);
0780: }
0781:
0782: public void destroyRoom(DestroyRoomRequest destroyRequest) {
0783: String alternateJID = destroyRequest.getAlternateJID();
0784: String reason = destroyRequest.getReason();
0785: MUCRole leaveRole;
0786: Collection<MUCRole> removedRoles = new ArrayList<MUCRole>();
0787: lock.writeLock().lock();
0788: try {
0789: boolean hasRemoteOccupants = false;
0790: // Remove each occupant
0791: for (String nickname : occupants.keySet()) {
0792: leaveRole = occupants.remove(nickname);
0793:
0794: if (leaveRole != null) {
0795: // Add the removed occupant to the list of removed occupants. We are keeping a
0796: // list of removed occupants to process later outside of the lock.
0797: if (leaveRole.isLocal()) {
0798: removedRoles.add(leaveRole);
0799: } else {
0800: hasRemoteOccupants = true;
0801: }
0802: removeOccupantRole(leaveRole);
0803: }
0804: }
0805: endTime = System.currentTimeMillis();
0806: // Set that the room has been destroyed
0807: isDestroyed = true;
0808: if (destroyRequest.isOriginator()) {
0809: if (hasRemoteOccupants) {
0810: // Ask other cluster nodes to remove occupants since room is being destroyed
0811: CacheFactory.doClusterTask(new DestroyRoomRequest(
0812: this , alternateJID, reason));
0813: }
0814: // Removes the room from the list of rooms hosted in the server
0815: server.removeChatRoom(name);
0816: }
0817: } finally {
0818: lock.writeLock().unlock();
0819: }
0820: // Send an unavailable presence to each removed occupant
0821: for (MUCRole removedRole : removedRoles) {
0822: try {
0823: // Send a presence stanza of type "unavailable" to the occupant
0824: Presence presence = createPresence(Presence.Type.unavailable);
0825: presence.setFrom(removedRole.getRoleAddress());
0826:
0827: // A fragment containing the x-extension for room destruction.
0828: Element fragment = presence.addChildElement("x",
0829: "http://jabber.org/protocol/muc#user");
0830: Element item = fragment.addElement("item");
0831: item.addAttribute("affiliation", "none");
0832: item.addAttribute("role", "none");
0833: if (alternateJID != null && alternateJID.length() > 0) {
0834: fragment.addElement("destroy").addAttribute("jid",
0835: alternateJID);
0836: }
0837: if (reason != null && reason.length() > 0) {
0838: Element destroy = fragment.element("destroy");
0839: if (destroy == null) {
0840: destroy = fragment.addElement("destroy");
0841: }
0842: destroy.addElement("reason").setText(reason);
0843: }
0844: removedRole.send(presence);
0845: } catch (Exception e) {
0846: Log.error(e);
0847: }
0848: }
0849: if (destroyRequest.isOriginator()) {
0850: // Remove the room from the DB if the room was persistent
0851: MUCPersistenceManager.deleteFromDB(this );
0852: }
0853: // Fire event that the room has been destroyed
0854: server.fireRoomDestroyed(getRole().getRoleAddress());
0855: }
0856:
0857: public void destroyRoom(String alternateJID, String reason) {
0858: DestroyRoomRequest destroyRequest = new DestroyRoomRequest(
0859: this , alternateJID, reason);
0860: destroyRequest.setOriginator(true);
0861: destroyRequest.run();
0862: }
0863:
0864: public Presence createPresence(Presence.Type presenceType)
0865: throws UnauthorizedException {
0866: Presence presence = new Presence();
0867: presence.setType(presenceType);
0868: presence.setFrom(role.getRoleAddress());
0869: return presence;
0870: }
0871:
0872: public void serverBroadcast(String msg) {
0873: Message message = new Message();
0874: message.setType(Message.Type.groupchat);
0875: message.setBody(msg);
0876: message.setFrom(role.getRoleAddress());
0877: broadcast(message);
0878: }
0879:
0880: public void sendPublicMessage(Message message, MUCRole senderRole)
0881: throws ForbiddenException {
0882: // Check that if the room is moderated then the sender of the message has to have voice
0883: if (isModerated()
0884: && senderRole.getRole().compareTo(
0885: MUCRole.Role.participant) > 0) {
0886: throw new ForbiddenException();
0887: }
0888: // Send the message to all occupants
0889: message.setFrom(senderRole.getRoleAddress());
0890: send(message);
0891: // Fire event that message was receibed by the room
0892: server.fireMessageReceived(getRole().getRoleAddress(),
0893: senderRole.getUserAddress(), senderRole.getNickname(),
0894: message);
0895: }
0896:
0897: public void sendPrivatePacket(Packet packet, MUCRole senderRole)
0898: throws NotFoundException {
0899: String resource = packet.getTo().getResource();
0900: MUCRole occupant = occupants.get(resource.toLowerCase());
0901: if (occupant != null) {
0902: packet.setFrom(senderRole.getRoleAddress());
0903: occupant.send(packet);
0904: } else {
0905: throw new NotFoundException();
0906: }
0907: }
0908:
0909: public void send(Packet packet) {
0910: if (packet instanceof Message) {
0911: broadcast((Message) packet);
0912: } else if (packet instanceof Presence) {
0913: broadcastPresence((Presence) packet);
0914: } else if (packet instanceof IQ) {
0915: IQ reply = IQ.createResultIQ((IQ) packet);
0916: reply.setChildElement(((IQ) packet).getChildElement());
0917: reply.setError(PacketError.Condition.bad_request);
0918: router.route(reply);
0919: }
0920: }
0921:
0922: /**
0923: * Broadcasts the specified presence to all room occupants. If the presence belongs to a
0924: * user whose role cannot be broadcast then the presence will only be sent to the presence's
0925: * user. On the other hand, the JID of the user that sent the presence won't be included if the
0926: * room is semi-anon and the target occupant is not a moderator.
0927: *
0928: * @param presence the presence to broadcast.
0929: */
0930: private void broadcastPresence(Presence presence) {
0931: if (presence == null) {
0932: return;
0933: }
0934: if (hasToCheckRoleToBroadcastPresence()) {
0935: Element frag = presence.getChildElement("x",
0936: "http://jabber.org/protocol/muc#user");
0937: // Check if we can broadcast the presence for this role
0938: if (!canBroadcastPresence(frag.element("item")
0939: .attributeValue("role"))) {
0940: // Just send the presence to the sender of the presence
0941: try {
0942: MUCRole occupant = getOccupant(presence.getFrom()
0943: .getResource());
0944: occupant.send(presence);
0945: } catch (UserNotFoundException e) {
0946: // Do nothing
0947: }
0948: return;
0949: }
0950: }
0951:
0952: // Broadcast presence to occupants hosted by other cluster nodes
0953: BroascastPresenceRequest request = new BroascastPresenceRequest(
0954: this , presence);
0955: CacheFactory.doClusterTask(request);
0956:
0957: // Broadcast presence to occupants connected to this JVM
0958: request = new BroascastPresenceRequest(this , presence);
0959: request.setOriginator(true);
0960: request.run();
0961: }
0962:
0963: public void broadcast(BroascastPresenceRequest presenceRequest) {
0964: String jid = null;
0965: Presence presence = presenceRequest.getPresence();
0966: Element frag = presence.getChildElement("x",
0967: "http://jabber.org/protocol/muc#user");
0968: // Don't include the occupant's JID if the room is semi-anon and the new occupant
0969: // is not a moderator
0970: if (!canAnyoneDiscoverJID()) {
0971: jid = frag.element("item").attributeValue("jid");
0972: }
0973: for (MUCRole occupant : occupants.values()) {
0974: if (!occupant.isLocal()) {
0975: continue;
0976: }
0977: // Don't include the occupant's JID if the room is semi-anon and the new occupant
0978: // is not a moderator
0979: if (!canAnyoneDiscoverJID()) {
0980: if (MUCRole.Role.moderator == occupant.getRole()) {
0981: frag.element("item").addAttribute("jid", jid);
0982: } else {
0983: frag.element("item").addAttribute("jid", null);
0984: }
0985: }
0986: occupant.send(presence);
0987: }
0988: }
0989:
0990: private void broadcast(Message message) {
0991: // Broadcast message to occupants hosted by other cluster nodes
0992: BroascastMessageRequest request = new BroascastMessageRequest(
0993: this , message, occupants.size());
0994: CacheFactory.doClusterTask(request);
0995:
0996: // Broadcast message to occupants connected to this JVM
0997: request = new BroascastMessageRequest(this , message, occupants
0998: .size());
0999: request.setOriginator(true);
1000: request.run();
1001: }
1002:
1003: public void broadcast(BroascastMessageRequest messageRequest) {
1004: Message message = messageRequest.getMessage();
1005: // Add message to the room history
1006: roomHistory.addMessage(message);
1007: // Send message to occupants connected to this JVM
1008: for (MUCRole occupant : occupants.values()) {
1009: // Do not send broadcast messages to deaf occupants or occupants hosted in
1010: // other cluster nodes
1011: if (occupant.isLocal() && !occupant.isVoiceOnly()) {
1012: occupant.send(message);
1013: }
1014: }
1015: if (messageRequest.isOriginator() && isLogEnabled()) {
1016: MUCRole senderRole = null;
1017: JID senderAddress;
1018: if (message.getFrom() != null
1019: && message.getFrom().getResource() != null) {
1020: senderRole = occupants.get(message.getFrom()
1021: .getResource().toLowerCase());
1022: }
1023: if (senderRole == null) {
1024: // The room itself is sending the message
1025: senderAddress = getRole().getRoleAddress();
1026: } else {
1027: // An occupant is sending the message
1028: senderAddress = senderRole.getUserAddress();
1029: }
1030: // Log the conversation
1031: server.logConversation(this , message, senderAddress);
1032: }
1033: server.messageBroadcastedTo(messageRequest.getOccupants());
1034: }
1035:
1036: /**
1037: * An empty role that represents the room itself in the chatroom. Chatrooms need to be able to
1038: * speak (server messages) and so must have their own role in the chatroom.
1039: */
1040: private class RoomRole implements MUCRole {
1041:
1042: private MUCRoom room;
1043:
1044: private RoomRole(MUCRoom room) {
1045: this .room = room;
1046: }
1047:
1048: public Presence getPresence() {
1049: return null;
1050: }
1051:
1052: public void setPresence(Presence presence) {
1053: }
1054:
1055: public void setRole(MUCRole.Role newRole) {
1056: }
1057:
1058: public MUCRole.Role getRole() {
1059: return MUCRole.Role.moderator;
1060: }
1061:
1062: public void setAffiliation(MUCRole.Affiliation newAffiliation) {
1063: }
1064:
1065: public MUCRole.Affiliation getAffiliation() {
1066: return MUCRole.Affiliation.owner;
1067: }
1068:
1069: public void changeNickname(String nickname) {
1070: }
1071:
1072: public String getNickname() {
1073: return null;
1074: }
1075:
1076: public boolean isVoiceOnly() {
1077: return false;
1078: }
1079:
1080: public boolean isLocal() {
1081: return true;
1082: }
1083:
1084: public NodeID getNodeID() {
1085: return XMPPServer.getInstance().getNodeID();
1086: }
1087:
1088: public MUCRoom getChatRoom() {
1089: return room;
1090: }
1091:
1092: private JID crJID = null;
1093:
1094: public JID getRoleAddress() {
1095: if (crJID == null) {
1096: crJID = new JID(room.getName(), server
1097: .getServiceDomain(), null, true);
1098: }
1099: return crJID;
1100: }
1101:
1102: public JID getUserAddress() {
1103: return null;
1104: }
1105:
1106: public void send(Packet packet) {
1107: room.send(packet);
1108: }
1109:
1110: public void destroy() {
1111: }
1112: }
1113:
1114: public long getChatLength() {
1115: return endTime - startTime;
1116: }
1117:
1118: /**
1119: * Updates all the presences of the given user with the new affiliation and role information. Do
1120: * nothing if the given jid is not present in the room. If the user has joined the room from
1121: * several client resources, all his/her occupants' presences will be updated.
1122: *
1123: * @param bareJID the bare jid of the user to update his/her role.
1124: * @param newAffiliation the new affiliation for the JID.
1125: * @param newRole the new role for the JID.
1126: * @return the list of updated presences of all the client resources that the client used to
1127: * join the room.
1128: * @throws NotAllowedException If trying to change the moderator role to an owner or an admin or
1129: * if trying to ban an owner or an administrator.
1130: */
1131: private List<Presence> changeOccupantAffiliation(String bareJID,
1132: MUCRole.Affiliation newAffiliation, MUCRole.Role newRole)
1133: throws NotAllowedException {
1134: List<Presence> presences = new ArrayList<Presence>();
1135: // Get all the roles (i.e. occupants) of this user based on his/her bare JID
1136: List<MUCRole> roles = occupantsByBareJID.get(bareJID);
1137: if (roles == null) {
1138: return presences;
1139: }
1140: // Collect all the updated presences of these roles
1141: for (MUCRole role : roles) {
1142: // Update the presence with the new affiliation and role
1143: if (role.isLocal()) {
1144: role.setAffiliation(newAffiliation);
1145: role.setRole(newRole);
1146: // Notify the othe cluster nodes to update the occupant
1147: CacheFactory.doClusterTask(new UpdateOccupant(this ,
1148: role));
1149: // Prepare a new presence to be sent to all the room occupants
1150: presences.add(role.getPresence().createCopy());
1151: } else {
1152: // Ask the cluster node hosting the occupant to make the changes. Note that if the change
1153: // is not allowed a NotAllowedException will be thrown
1154: Element element = (Element) CacheFactory
1155: .doSynchronousClusterTask(
1156: new UpdateOccupantRequest(this , role
1157: .getNickname(), newAffiliation,
1158: newRole), role.getNodeID()
1159: .toByteArray());
1160: if (element != null) {
1161: // Prepare a new presence to be sent to all the room occupants
1162: presences.add(new Presence(element, true));
1163: } else {
1164: throw new NotAllowedException();
1165: }
1166: }
1167: }
1168: // Answer all the updated presences
1169: return presences;
1170: }
1171:
1172: /**
1173: * Updates the presence of the given user with the new role information. Do nothing if the given
1174: * jid is not present in the room.
1175: *
1176: * @param jid the full jid of the user to update his/her role.
1177: * @param newRole the new role for the JID.
1178: * @return the updated presence of the user or null if none.
1179: * @throws NotAllowedException If trying to change the moderator role to an owner or an admin.
1180: */
1181: private Presence changeOccupantRole(JID jid, MUCRole.Role newRole)
1182: throws NotAllowedException {
1183: // Try looking the role in the bare JID list
1184: MUCRole role = occupantsByFullJID.get(jid);
1185: if (role != null) {
1186: // Update the presence with the new role
1187: role.setRole(newRole);
1188: // Notify the othe cluster nodes to update the occupant
1189: CacheFactory.doClusterTask(new UpdateOccupant(this , role));
1190: // Prepare a new presence to be sent to all the room occupants
1191: return role.getPresence().createCopy();
1192: }
1193: return null;
1194: }
1195:
1196: public void addFirstOwner(String bareJID) {
1197: owners.add(bareJID);
1198: }
1199:
1200: public List<Presence> addOwner(String bareJID, MUCRole sendRole)
1201: throws ForbiddenException {
1202: MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
1203: if (MUCRole.Affiliation.owner != sendRole.getAffiliation()) {
1204: throw new ForbiddenException();
1205: }
1206: // Check if user is already an owner
1207: if (owners.contains(bareJID)) {
1208: // Do nothing
1209: return Collections.emptyList();
1210: }
1211: owners.add(bareJID);
1212: // Remove the user from other affiliation lists
1213: if (removeAdmin(bareJID)) {
1214: oldAffiliation = MUCRole.Affiliation.admin;
1215: } else if (removeMember(bareJID)) {
1216: oldAffiliation = MUCRole.Affiliation.member;
1217: } else if (removeOutcast(bareJID)) {
1218: oldAffiliation = MUCRole.Affiliation.outcast;
1219: }
1220: // Update the DB if the room is persistent
1221: MUCPersistenceManager.saveAffiliationToDB(this , bareJID, null,
1222: MUCRole.Affiliation.owner, oldAffiliation);
1223: // Update the presence with the new affiliation and inform all occupants
1224: try {
1225: return changeOccupantAffiliation(bareJID,
1226: MUCRole.Affiliation.owner, MUCRole.Role.moderator);
1227: } catch (NotAllowedException e) {
1228: // We should never receive this exception....in theory
1229: return null;
1230: }
1231: }
1232:
1233: private boolean removeOwner(String bareJID) {
1234: return owners.remove(bareJID);
1235: }
1236:
1237: public List<Presence> addAdmin(String bareJID, MUCRole sendRole)
1238: throws ForbiddenException, ConflictException {
1239: MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
1240: if (MUCRole.Affiliation.owner != sendRole.getAffiliation()) {
1241: throw new ForbiddenException();
1242: }
1243: // Check that the room always has an owner
1244: if (owners.contains(bareJID) && owners.size() == 1) {
1245: throw new ConflictException();
1246: }
1247: // Check if user is already an admin
1248: if (admins.contains(bareJID)) {
1249: // Do nothing
1250: return Collections.emptyList();
1251: }
1252: admins.add(bareJID);
1253: // Remove the user from other affiliation lists
1254: if (removeOwner(bareJID)) {
1255: oldAffiliation = MUCRole.Affiliation.owner;
1256: } else if (removeMember(bareJID)) {
1257: oldAffiliation = MUCRole.Affiliation.member;
1258: } else if (removeOutcast(bareJID)) {
1259: oldAffiliation = MUCRole.Affiliation.outcast;
1260: }
1261: // Update the DB if the room is persistent
1262: MUCPersistenceManager.saveAffiliationToDB(this , bareJID, null,
1263: MUCRole.Affiliation.admin, oldAffiliation);
1264: // Update the presence with the new affiliation and inform all occupants
1265: try {
1266: return changeOccupantAffiliation(bareJID,
1267: MUCRole.Affiliation.admin, MUCRole.Role.moderator);
1268: } catch (NotAllowedException e) {
1269: // We should never receive this exception....in theory
1270: return null;
1271: }
1272: }
1273:
1274: private boolean removeAdmin(String bareJID) {
1275: return admins.remove(bareJID);
1276: }
1277:
1278: public List<Presence> addMember(String bareJID, String nickname,
1279: MUCRole sendRole) throws ForbiddenException,
1280: ConflictException {
1281: MUCRole.Affiliation oldAffiliation = (members
1282: .containsKey(bareJID) ? MUCRole.Affiliation.member
1283: : MUCRole.Affiliation.none);
1284: if (isMembersOnly()) {
1285: if (!canOccupantsInvite()) {
1286: if (MUCRole.Affiliation.admin != sendRole
1287: .getAffiliation()
1288: && MUCRole.Affiliation.owner != sendRole
1289: .getAffiliation()) {
1290: throw new ForbiddenException();
1291: }
1292: }
1293: } else {
1294: if (MUCRole.Affiliation.admin != sendRole.getAffiliation()
1295: && MUCRole.Affiliation.owner != sendRole
1296: .getAffiliation()) {
1297: throw new ForbiddenException();
1298: }
1299: }
1300: // Check if the desired nickname is already reserved for another member
1301: if (nickname != null && nickname.trim().length() > 0
1302: && members.containsValue(nickname)) {
1303: if (!nickname.equals(members.get(bareJID))) {
1304: throw new ConflictException();
1305: }
1306: }
1307: // Check that the room always has an owner
1308: if (owners.contains(bareJID) && owners.size() == 1) {
1309: throw new ConflictException();
1310: }
1311: // Associate the reserved nickname with the bareJID. If nickname is null then associate an
1312: // empty string
1313: members.put(bareJID, (nickname == null ? "" : nickname));
1314: // Remove the user from other affiliation lists
1315: if (removeOwner(bareJID)) {
1316: oldAffiliation = MUCRole.Affiliation.owner;
1317: } else if (removeAdmin(bareJID)) {
1318: oldAffiliation = MUCRole.Affiliation.admin;
1319: } else if (removeOutcast(bareJID)) {
1320: oldAffiliation = MUCRole.Affiliation.outcast;
1321: }
1322: // Update the DB if the room is persistent
1323: MUCPersistenceManager.saveAffiliationToDB(this , bareJID,
1324: nickname, MUCRole.Affiliation.member, oldAffiliation);
1325: // Update the presence with the new affiliation and inform all occupants
1326: try {
1327: return changeOccupantAffiliation(bareJID,
1328: MUCRole.Affiliation.member,
1329: MUCRole.Role.participant);
1330: } catch (NotAllowedException e) {
1331: // We should never receive this exception....in theory
1332: return null;
1333: }
1334: }
1335:
1336: private boolean removeMember(String bareJID) {
1337: boolean answer = members.containsKey(bareJID);
1338: members.remove(bareJID);
1339: return answer;
1340: }
1341:
1342: public List<Presence> addOutcast(String bareJID, String reason,
1343: MUCRole senderRole) throws NotAllowedException,
1344: ForbiddenException, ConflictException {
1345: MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
1346: if (MUCRole.Affiliation.admin != senderRole.getAffiliation()
1347: && MUCRole.Affiliation.owner != senderRole
1348: .getAffiliation()) {
1349: throw new ForbiddenException();
1350: }
1351: // Check that the room always has an owner
1352: if (owners.contains(bareJID) && owners.size() == 1) {
1353: throw new ConflictException();
1354: }
1355: // Check if user is already an outcast
1356: if (outcasts.contains(bareJID)) {
1357: // Do nothing
1358: return Collections.emptyList();
1359: }
1360: // Update the presence with the new affiliation and inform all occupants
1361: // actorJID will be null if the room itself (ie. via admin console) made the request
1362: JID actorJID = senderRole.getUserAddress();
1363: List<Presence> updatedPresences = changeOccupantAffiliation(
1364: bareJID, MUCRole.Affiliation.outcast, MUCRole.Role.none);
1365: Element frag;
1366: // Add the status code and reason why the user was banned to the presences that will
1367: // be sent to the room occupants (the banned user will not receive this presences)
1368: for (Presence presence : updatedPresences) {
1369: frag = presence.getChildElement("x",
1370: "http://jabber.org/protocol/muc#user");
1371: // Add the status code 301 that indicates that the user was banned
1372: frag.addElement("status").addAttribute("code", "301");
1373: // Add the reason why the user was banned
1374: if (reason != null && reason.trim().length() > 0) {
1375: frag.element("item").addElement("reason").setText(
1376: reason);
1377: }
1378:
1379: // Remove the banned users from the room. If a user has joined the room from
1380: // different client resources, he/she will be kicked from all the client resources
1381: // Effectively kick the occupant from the room
1382: kickPresence(presence, actorJID);
1383: }
1384: // Update the affiliation lists
1385: outcasts.add(bareJID);
1386: // Remove the user from other affiliation lists
1387: if (removeOwner(bareJID)) {
1388: oldAffiliation = MUCRole.Affiliation.owner;
1389: } else if (removeAdmin(bareJID)) {
1390: oldAffiliation = MUCRole.Affiliation.admin;
1391: } else if (removeMember(bareJID)) {
1392: oldAffiliation = MUCRole.Affiliation.member;
1393: }
1394: // Update the DB if the room is persistent
1395: MUCPersistenceManager.saveAffiliationToDB(this , bareJID, null,
1396: MUCRole.Affiliation.outcast, oldAffiliation);
1397: return updatedPresences;
1398: }
1399:
1400: private boolean removeOutcast(String bareJID) {
1401: return outcasts.remove(bareJID);
1402: }
1403:
1404: public List<Presence> addNone(String bareJID, MUCRole senderRole)
1405: throws ForbiddenException, ConflictException {
1406: MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
1407: if (MUCRole.Affiliation.admin != senderRole.getAffiliation()
1408: && MUCRole.Affiliation.owner != senderRole
1409: .getAffiliation()) {
1410: throw new ForbiddenException();
1411: }
1412: // Check that the room always has an owner
1413: if (owners.contains(bareJID) && owners.size() == 1) {
1414: throw new ConflictException();
1415: }
1416: List<Presence> updatedPresences = null;
1417: boolean wasMember = members.containsKey(bareJID)
1418: || admins.contains(bareJID) || owners.contains(bareJID);
1419: // Remove the user from ALL the affiliation lists
1420: if (removeOwner(bareJID)) {
1421: oldAffiliation = MUCRole.Affiliation.owner;
1422: } else if (removeAdmin(bareJID)) {
1423: oldAffiliation = MUCRole.Affiliation.admin;
1424: } else if (removeMember(bareJID)) {
1425: oldAffiliation = MUCRole.Affiliation.member;
1426: } else if (removeOutcast(bareJID)) {
1427: oldAffiliation = MUCRole.Affiliation.outcast;
1428: }
1429: // Remove the affiliation of this user from the DB if the room is persistent
1430: MUCPersistenceManager.removeAffiliationFromDB(this , bareJID,
1431: oldAffiliation);
1432:
1433: // Update the presence with the new affiliation and inform all occupants
1434: try {
1435: MUCRole.Role newRole;
1436: if (isMembersOnly() && wasMember) {
1437: newRole = MUCRole.Role.none;
1438: } else {
1439: newRole = isModerated() ? MUCRole.Role.visitor
1440: : MUCRole.Role.participant;
1441: }
1442: updatedPresences = changeOccupantAffiliation(bareJID,
1443: MUCRole.Affiliation.none, newRole);
1444: if (isMembersOnly() && wasMember) {
1445: // If the room is members-only, remove the user from the room including a status
1446: // code of 321 to indicate that the user was removed because of an affiliation
1447: // change
1448: Element frag;
1449: // Add the status code to the presences that will be sent to the room occupants
1450: for (Presence presence : updatedPresences) {
1451: // Set the presence as an unavailable presence
1452: presence.setType(Presence.Type.unavailable);
1453: presence.setStatus(null);
1454: frag = presence.getChildElement("x",
1455: "http://jabber.org/protocol/muc#user");
1456: // Add the status code 321 that indicates that the user was removed because of
1457: // an affiliation change
1458: frag.addElement("status").addAttribute("code",
1459: "321");
1460:
1461: // Remove the ex-member from the room. If a user has joined the room from
1462: // different client resources, he/she will be kicked from all the client
1463: // resources.
1464: // Effectively kick the occupant from the room
1465: JID actorJID = senderRole.getUserAddress();
1466: kickPresence(presence, actorJID);
1467: }
1468: }
1469: } catch (NotAllowedException e) {
1470: // We should never receive this exception....in theory
1471: }
1472: return updatedPresences;
1473: }
1474:
1475: public boolean isLocked() {
1476: return lockedTime > 0;
1477: }
1478:
1479: public boolean isManuallyLocked() {
1480: return lockedTime > 0 && creationDate.getTime() != lockedTime;
1481: }
1482:
1483: public void presenceUpdated(MUCRole occupantRole,
1484: Presence newPresence) {
1485: // Ask other cluster nodes to update the presence of the occupant
1486: UpdatePresence request = new UpdatePresence(this , newPresence
1487: .createCopy(), occupantRole.getNickname());
1488: CacheFactory.doClusterTask(request);
1489:
1490: // Update the presence of the occupant
1491: request = new UpdatePresence(this , newPresence.createCopy(),
1492: occupantRole.getNickname());
1493: request.setOriginator(true);
1494: request.run();
1495:
1496: // Broadcast new presence of occupant
1497: broadcastPresence(occupantRole.getPresence().createCopy());
1498: }
1499:
1500: /**
1501: * Updates the presence of an occupant with the new presence included in the request.
1502: *
1503: * @param updatePresence request to update an occupant's presence.
1504: */
1505: public void presenceUpdated(UpdatePresence updatePresence) {
1506: MUCRole occupantRole = occupants.get(updatePresence
1507: .getNickname().toLowerCase());
1508: if (occupantRole != null) {
1509: occupantRole.setPresence(updatePresence.getPresence());
1510: } else {
1511: Log
1512: .debug("LocalMUCRoom: Failed to update presence of room occupant. Occupant nickname: "
1513: + updatePresence.getNickname());
1514: }
1515: }
1516:
1517: public void occupantUpdated(UpdateOccupant update) {
1518: MUCRole occupantRole = occupants.get(update.getNickname()
1519: .toLowerCase());
1520: if (occupantRole != null) {
1521: if (!occupantRole.isLocal()) {
1522: occupantRole.setPresence(update.getPresence());
1523: try {
1524: occupantRole.setRole(update.getRole());
1525: occupantRole
1526: .setAffiliation(update.getAffiliation());
1527: } catch (NotAllowedException e) {
1528: // Ignore. Should never happen with remote roles
1529: }
1530: } else {
1531: Log
1532: .error("Tried to update local occupant with info of local occupant?. Occupant nickname: "
1533: + update.getNickname());
1534: }
1535: } else {
1536: Log
1537: .debug("LocalMUCRoom: Failed to update information of room occupant. Occupant nickname: "
1538: + update.getNickname());
1539: }
1540: }
1541:
1542: public Presence updateOccupant(UpdateOccupantRequest updateRequest)
1543: throws NotAllowedException {
1544: MUCRole occupantRole = occupants.get(updateRequest
1545: .getNickname().toLowerCase());
1546: if (occupantRole != null) {
1547: occupantRole.setAffiliation(updateRequest.getAffiliation());
1548: occupantRole.setRole(updateRequest.getRole());
1549: // Notify the othe cluster nodes to update the occupant
1550: CacheFactory.doClusterTask(new UpdateOccupant(this ,
1551: occupantRole));
1552: return occupantRole.getPresence();
1553: } else {
1554: Log
1555: .debug("LocalMUCRoom: Failed to update information of local room occupant. Occupant nickname: "
1556: + updateRequest.getNickname());
1557: }
1558: return null;
1559: }
1560:
1561: public void nicknameChanged(MUCRole occupantRole,
1562: Presence newPresence, String oldNick, String newNick) {
1563: // Ask other cluster nodes to update the nickname of the occupant
1564: ChangeNickname request = new ChangeNickname(this , oldNick,
1565: newNick, newPresence.createCopy());
1566: CacheFactory.doClusterTask(request);
1567:
1568: // Update the nickname of the occupant
1569: request = new ChangeNickname(this , oldNick, newNick,
1570: newPresence.createCopy());
1571: request.setOriginator(true);
1572: request.run();
1573:
1574: // Broadcast new presence of occupant
1575: broadcastPresence(occupantRole.getPresence().createCopy());
1576: }
1577:
1578: public void nicknameChanged(ChangeNickname changeNickname) {
1579: MUCRole occupantRole = occupants.get(changeNickname
1580: .getOldNick().toLowerCase());
1581: if (occupantRole != null) {
1582: // Update the role with the new info
1583: occupantRole.setPresence(changeNickname.getPresence());
1584: occupantRole.changeNickname(changeNickname.getNewNick());
1585: // Fire event that user changed his nickname
1586: server.fireNicknameChanged(getRole().getRoleAddress(),
1587: occupantRole.getUserAddress(), changeNickname
1588: .getOldNick(), changeNickname.getNewNick());
1589: // Associate the existing MUCRole with the new nickname
1590: occupants.put(changeNickname.getNewNick().toLowerCase(),
1591: occupantRole);
1592: // Remove the old nickname
1593: occupants.remove(changeNickname.getOldNick().toLowerCase());
1594: }
1595: }
1596:
1597: public void changeSubject(Message packet, MUCRole role)
1598: throws ForbiddenException {
1599: if ((canOccupantsChangeSubject() && role.getRole().compareTo(
1600: MUCRole.Role.visitor) < 0)
1601: || MUCRole.Role.moderator == role.getRole()) {
1602: // Do nothing if the new subject is the same as the existing one
1603: if (packet.getSubject().equals(subject)) {
1604: return;
1605: }
1606: // Set the new subject to the room
1607: subject = packet.getSubject();
1608: MUCPersistenceManager.updateRoomSubject(this );
1609: // Notify all the occupants that the subject has changed
1610: packet.setFrom(role.getRoleAddress());
1611: send(packet);
1612: } else {
1613: throw new ForbiddenException();
1614: }
1615: }
1616:
1617: public String getSubject() {
1618: return subject;
1619: }
1620:
1621: public void setSubject(String subject) {
1622: this .subject = subject;
1623: }
1624:
1625: public void sendInvitation(JID to, String reason,
1626: MUCRole senderRole, List<Element> extensions)
1627: throws ForbiddenException {
1628: if (!isMembersOnly()
1629: || canOccupantsInvite()
1630: || MUCRole.Affiliation.admin == senderRole
1631: .getAffiliation()
1632: || MUCRole.Affiliation.owner == senderRole
1633: .getAffiliation()) {
1634: // If the room is not members-only OR if the room is members-only and anyone can send
1635: // invitations or the sender is an admin or an owner, then send the invitation
1636: Message message = new Message();
1637: message.setFrom(role.getRoleAddress());
1638: message.setTo(to);
1639: // Add a list of extensions sent with the original message invitation (if any)
1640: if (extensions != null) {
1641: for (Element element : extensions) {
1642: element.setParent(null);
1643: message.getElement().add(element);
1644: }
1645: }
1646: Element frag = message.addChildElement("x",
1647: "http://jabber.org/protocol/muc#user");
1648: // ChatUser will be null if the room itself (ie. via admin console) made the request
1649: if (senderRole.getUserAddress() != null) {
1650: frag.addElement("invite").addAttribute("from",
1651: senderRole.getUserAddress().toBareJID());
1652: }
1653: if (reason != null && reason.length() > 0) {
1654: Element invite = frag.element("invite");
1655: if (invite == null) {
1656: invite = frag.addElement("invite");
1657: }
1658: invite.addElement("reason").setText(reason);
1659: }
1660: if (isPasswordProtected()) {
1661: frag.addElement("password").setText(getPassword());
1662: }
1663:
1664: // Include the jabber:x:conference information for backward compatibility
1665: frag = message.addChildElement("x", "jabber:x:conference");
1666: frag.addAttribute("jid", role.getRoleAddress().toBareJID());
1667:
1668: // Send the message with the invitation
1669: router.route(message);
1670: } else {
1671: throw new ForbiddenException();
1672: }
1673: }
1674:
1675: public void sendInvitationRejection(JID to, String reason,
1676: JID sender) {
1677: Message message = new Message();
1678: message.setFrom(role.getRoleAddress());
1679: message.setTo(to);
1680: Element frag = message.addChildElement("x",
1681: "http://jabber.org/protocol/muc#user");
1682: frag.addElement("decline").addAttribute("from",
1683: sender.toBareJID());
1684: if (reason != null && reason.length() > 0) {
1685: frag.element("decline").addElement("reason")
1686: .setText(reason);
1687: }
1688:
1689: // Send the message with the invitation
1690: router.route(message);
1691: }
1692:
1693: public IQOwnerHandler getIQOwnerHandler() {
1694: return iqOwnerHandler;
1695: }
1696:
1697: public IQAdminHandler getIQAdminHandler() {
1698: return iqAdminHandler;
1699: }
1700:
1701: public MUCRoomHistory getRoomHistory() {
1702: return roomHistory;
1703: }
1704:
1705: public Collection<String> getOwners() {
1706: return Collections.unmodifiableList(owners);
1707: }
1708:
1709: public Collection<String> getAdmins() {
1710: return Collections.unmodifiableList(admins);
1711: }
1712:
1713: public Collection<String> getMembers() {
1714: return Collections.unmodifiableMap(members).keySet();
1715: }
1716:
1717: public Collection<String> getOutcasts() {
1718: return Collections.unmodifiableList(outcasts);
1719: }
1720:
1721: public Collection<MUCRole> getModerators() {
1722: List<MUCRole> moderators = new ArrayList<MUCRole>();
1723: for (MUCRole role : occupants.values()) {
1724: if (MUCRole.Role.moderator == role.getRole()) {
1725: moderators.add(role);
1726: }
1727: }
1728: return moderators;
1729: }
1730:
1731: public Collection<MUCRole> getParticipants() {
1732: List<MUCRole> participants = new ArrayList<MUCRole>();
1733: for (MUCRole role : occupants.values()) {
1734: if (MUCRole.Role.participant == role.getRole()) {
1735: participants.add(role);
1736: }
1737: }
1738: return participants;
1739: }
1740:
1741: public Presence addModerator(JID jid, MUCRole senderRole)
1742: throws ForbiddenException {
1743: if (MUCRole.Affiliation.admin != senderRole.getAffiliation()
1744: && MUCRole.Affiliation.owner != senderRole
1745: .getAffiliation()) {
1746: throw new ForbiddenException();
1747: }
1748: // Update the presence with the new role and inform all occupants
1749: try {
1750: return changeOccupantRole(jid, MUCRole.Role.moderator);
1751: } catch (NotAllowedException e) {
1752: // We should never receive this exception....in theory
1753: return null;
1754: }
1755: }
1756:
1757: public Presence addParticipant(JID jid, String reason,
1758: MUCRole senderRole) throws NotAllowedException,
1759: ForbiddenException {
1760: if (MUCRole.Role.moderator != senderRole.getRole()) {
1761: throw new ForbiddenException();
1762: }
1763: // Update the presence with the new role and inform all occupants
1764: Presence updatedPresence = changeOccupantRole(jid,
1765: MUCRole.Role.participant);
1766: if (updatedPresence != null) {
1767: Element frag = updatedPresence.getChildElement("x",
1768: "http://jabber.org/protocol/muc#user");
1769: // Add the reason why the user was granted voice
1770: if (reason != null && reason.trim().length() > 0) {
1771: frag.element("item").addElement("reason").setText(
1772: reason);
1773: }
1774: }
1775: return updatedPresence;
1776: }
1777:
1778: public Presence addVisitor(JID jid, MUCRole senderRole)
1779: throws NotAllowedException, ForbiddenException {
1780: if (MUCRole.Role.moderator != senderRole.getRole()) {
1781: throw new ForbiddenException();
1782: }
1783: return changeOccupantRole(jid, MUCRole.Role.visitor);
1784: }
1785:
1786: public Presence kickOccupant(JID jid, JID actorJID, String reason)
1787: throws NotAllowedException {
1788: // Update the presence with the new role and inform all occupants
1789: Presence updatedPresence = changeOccupantRole(jid,
1790: MUCRole.Role.none);
1791: if (updatedPresence != null) {
1792: Element frag = updatedPresence.getChildElement("x",
1793: "http://jabber.org/protocol/muc#user");
1794:
1795: // Add the status code 307 that indicates that the user was kicked
1796: frag.addElement("status").addAttribute("code", "307");
1797: // Add the reason why the user was kicked
1798: if (reason != null && reason.trim().length() > 0) {
1799: frag.element("item").addElement("reason").setText(
1800: reason);
1801: }
1802:
1803: // Effectively kick the occupant from the room
1804: kickPresence(updatedPresence, actorJID);
1805: }
1806: return updatedPresence;
1807: }
1808:
1809: /**
1810: * Kicks the occupant from the room. This means that the occupant will receive an unavailable
1811: * presence with the actor that initiated the kick (if any). The occupant will also be removed
1812: * from the occupants lists.
1813: *
1814: * @param kickPresence the presence of the occupant to kick from the room.
1815: * @param actorJID The JID of the actor that initiated the kick or <tt>null</tt> if the info
1816: * was not provided.
1817: */
1818: private void kickPresence(Presence kickPresence, JID actorJID) {
1819: MUCRole kickedRole;
1820: // Get the role to kick
1821: kickedRole = occupants.get(kickPresence.getFrom().getResource()
1822: .toLowerCase());
1823: if (kickedRole != null) {
1824: kickPresence = kickPresence.createCopy();
1825: // Add the actor's JID that kicked this user from the room
1826: if (actorJID != null && actorJID.toString().length() > 0) {
1827: Element frag = kickPresence.getChildElement("x",
1828: "http://jabber.org/protocol/muc#user");
1829: frag.element("item").addElement("actor").addAttribute(
1830: "jid", actorJID.toBareJID());
1831: }
1832: // Send the unavailable presence to the banned user
1833: kickedRole.send(kickPresence);
1834: // Remove the occupant from the room's occupants lists
1835: OccupantLeftEvent event = new OccupantLeftEvent(this ,
1836: kickedRole);
1837: event.setOriginator(true);
1838: event.run();
1839:
1840: // Remove the occupant from the room's occupants lists
1841: event = new OccupantLeftEvent(this , kickedRole);
1842: CacheFactory.doClusterTask(event);
1843: }
1844: }
1845:
1846: public boolean canAnyoneDiscoverJID() {
1847: return canAnyoneDiscoverJID;
1848: }
1849:
1850: public void setCanAnyoneDiscoverJID(boolean canAnyoneDiscoverJID) {
1851: this .canAnyoneDiscoverJID = canAnyoneDiscoverJID;
1852: }
1853:
1854: public boolean canOccupantsChangeSubject() {
1855: return canOccupantsChangeSubject;
1856: }
1857:
1858: public void setCanOccupantsChangeSubject(
1859: boolean canOccupantsChangeSubject) {
1860: this .canOccupantsChangeSubject = canOccupantsChangeSubject;
1861: }
1862:
1863: public boolean canOccupantsInvite() {
1864: return canOccupantsInvite;
1865: }
1866:
1867: public void setCanOccupantsInvite(boolean canOccupantsInvite) {
1868: this .canOccupantsInvite = canOccupantsInvite;
1869: }
1870:
1871: public String getNaturalLanguageName() {
1872: return naturalLanguageName;
1873: }
1874:
1875: public void setNaturalLanguageName(String naturalLanguageName) {
1876: this .naturalLanguageName = naturalLanguageName;
1877: }
1878:
1879: public String getDescription() {
1880: return description;
1881: }
1882:
1883: public void setDescription(String description) {
1884: this .description = description;
1885: }
1886:
1887: public boolean isMembersOnly() {
1888: return membersOnly;
1889: }
1890:
1891: public List<Presence> setMembersOnly(boolean membersOnly) {
1892: List<Presence> presences = new ArrayList<Presence>();
1893: if (membersOnly && !this .membersOnly) {
1894: // If the room was not members-only and now it is, kick occupants that aren't member
1895: // of the room
1896: for (MUCRole occupant : occupants.values()) {
1897: if (occupant.getAffiliation().compareTo(
1898: MUCRole.Affiliation.member) > 0) {
1899: try {
1900: presences
1901: .add(kickOccupant(
1902: occupant.getRoleAddress(),
1903: null,
1904: LocaleUtils
1905: .getLocalizedString("muc.roomIsNowMembersOnly")));
1906: } catch (NotAllowedException e) {
1907: Log.error(e);
1908: }
1909: }
1910: }
1911: }
1912: this .membersOnly = membersOnly;
1913: return presences;
1914: }
1915:
1916: public boolean isLogEnabled() {
1917: return logEnabled;
1918: }
1919:
1920: public void setLogEnabled(boolean logEnabled) {
1921: this .logEnabled = logEnabled;
1922: }
1923:
1924: public void setLoginRestrictedToNickname(boolean restricted) {
1925: this .loginRestrictedToNickname = restricted;
1926: }
1927:
1928: public boolean isLoginRestrictedToNickname() {
1929: return loginRestrictedToNickname;
1930: }
1931:
1932: public void setChangeNickname(boolean canChange) {
1933: this .canChangeNickname = canChange;
1934: }
1935:
1936: public boolean canChangeNickname() {
1937: return canChangeNickname;
1938: }
1939:
1940: public void setRegistrationEnabled(boolean registrationEnabled) {
1941: this .registrationEnabled = registrationEnabled;
1942: }
1943:
1944: public boolean isRegistrationEnabled() {
1945: return registrationEnabled;
1946: }
1947:
1948: public int getMaxUsers() {
1949: return maxUsers;
1950: }
1951:
1952: public void setMaxUsers(int maxUsers) {
1953: this .maxUsers = maxUsers;
1954: }
1955:
1956: public boolean isModerated() {
1957: return moderated;
1958: }
1959:
1960: public void setModerated(boolean moderated) {
1961: this .moderated = moderated;
1962: }
1963:
1964: public String getPassword() {
1965: return password;
1966: }
1967:
1968: public void setPassword(String password) {
1969: this .password = password;
1970: }
1971:
1972: public boolean isPasswordProtected() {
1973: return password != null && password.trim().length() > 0;
1974: }
1975:
1976: public boolean isPersistent() {
1977: return persistent;
1978: }
1979:
1980: public boolean wasSavedToDB() {
1981: return isPersistent() && savedToDB;
1982: }
1983:
1984: public void setSavedToDB(boolean saved) {
1985: this .savedToDB = saved;
1986: }
1987:
1988: public void setPersistent(boolean persistent) {
1989: this .persistent = persistent;
1990: }
1991:
1992: public boolean isPublicRoom() {
1993: return !isDestroyed && publicRoom;
1994: }
1995:
1996: public void setPublicRoom(boolean publicRoom) {
1997: this .publicRoom = publicRoom;
1998: }
1999:
2000: public List<String> getRolesToBroadcastPresence() {
2001: return Collections.unmodifiableList(rolesToBroadcastPresence);
2002: }
2003:
2004: public void setRolesToBroadcastPresence(
2005: List<String> rolesToBroadcastPresence) {
2006: // TODO If the list changes while there are occupants in the room we must send available or
2007: // unavailable presences of the affected occupants to the rest of the occupants
2008: this .rolesToBroadcastPresence = rolesToBroadcastPresence;
2009: }
2010:
2011: /**
2012: * Returns true if we need to check whether a presence could be sent or not.
2013: *
2014: * @return true if we need to check whether a presence could be sent or not.
2015: */
2016: private boolean hasToCheckRoleToBroadcastPresence() {
2017: // For performance reasons the check is done based on the size of the collection.
2018: return rolesToBroadcastPresence.size() < 3;
2019: }
2020:
2021: public boolean canBroadcastPresence(String roleToBroadcast) {
2022: return "none".equals(roleToBroadcast)
2023: || rolesToBroadcastPresence.contains(roleToBroadcast);
2024: }
2025:
2026: public void lock(MUCRole senderRole) throws ForbiddenException {
2027: if (MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
2028: throw new ForbiddenException();
2029: }
2030: if (isLocked()) {
2031: // Do nothing if the room was already locked
2032: return;
2033: }
2034: setLocked(true);
2035: if (senderRole.getUserAddress() != null) {
2036: // Send to the occupant that locked the room a message saying so
2037: Message message = new Message();
2038: message.setType(Message.Type.groupchat);
2039: message.setBody(LocaleUtils
2040: .getLocalizedString("muc.locked"));
2041: message.setFrom(getRole().getRoleAddress());
2042: senderRole.send(message);
2043: }
2044: }
2045:
2046: public void unlock(MUCRole senderRole) throws ForbiddenException {
2047: if (MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
2048: throw new ForbiddenException();
2049: }
2050: if (!isLocked()) {
2051: // Do nothing if the room was already unlocked
2052: return;
2053: }
2054: setLocked(false);
2055: if (senderRole.getUserAddress() != null) {
2056: // Send to the occupant that unlocked the room a message saying so
2057: Message message = new Message();
2058: message.setType(Message.Type.groupchat);
2059: message.setBody(LocaleUtils
2060: .getLocalizedString("muc.unlocked"));
2061: message.setFrom(getRole().getRoleAddress());
2062: senderRole.send(message);
2063: }
2064: }
2065:
2066: private void setLocked(boolean locked) {
2067: if (locked) {
2068: this .lockedTime = System.currentTimeMillis();
2069: } else {
2070: this .lockedTime = 0;
2071: }
2072: MUCPersistenceManager.updateRoomLock(this );
2073: }
2074:
2075: /**
2076: * Sets the date when the room was locked. Initially when the room is created it is locked so
2077: * the locked date is the creation date of the room. Afterwards, the room may be manually
2078: * locked and unlocked so the locked date may be in these cases different than the creation
2079: * date. A Date with time 0 means that the the room is unlocked.
2080: *
2081: * @param lockedTime the date when the room was locked.
2082: */
2083: void setLockedDate(Date lockedTime) {
2084: this .lockedTime = lockedTime.getTime();
2085: }
2086:
2087: /**
2088: * Returns the date when the room was locked. Initially when the room is created it is locked so
2089: * the locked date is the creation date of the room. Afterwards, the room may be manually
2090: * locked and unlocked so the locked date may be in these cases different than the creation
2091: * date. When the room is unlocked a Date with time 0 is returned.
2092: *
2093: * @return the date when the room was locked.
2094: */
2095: Date getLockedDate() {
2096: return new Date(lockedTime);
2097: }
2098:
2099: public List<Presence> addAdmins(List<String> newAdmins,
2100: MUCRole senderRole) throws ForbiddenException,
2101: ConflictException {
2102: List<Presence> answer = new ArrayList<Presence>(newAdmins
2103: .size());
2104: for (String newAdmin : newAdmins) {
2105: if (newAdmin.trim().length() > 0
2106: && !admins.contains(newAdmin)) {
2107: answer.addAll(addAdmin(newAdmin, senderRole));
2108: }
2109: }
2110: return answer;
2111: }
2112:
2113: public List<Presence> addOwners(List<String> newOwners,
2114: MUCRole senderRole) throws ForbiddenException {
2115: List<Presence> answer = new ArrayList<Presence>(newOwners
2116: .size());
2117: for (String newOwner : newOwners) {
2118: if (newOwner.trim().length() > 0
2119: && !owners.contains(newOwner)) {
2120: answer.addAll(addOwner(newOwner, senderRole));
2121: }
2122: }
2123: return answer;
2124: }
2125:
2126: public void saveToDB() {
2127: // Make the room persistent
2128: MUCPersistenceManager.saveToDB(this );
2129: if (!savedToDB) {
2130: // Set that the room is now in the DB
2131: savedToDB = true;
2132: // Save the existing room owners to the DB
2133: for (String owner : owners) {
2134: MUCPersistenceManager.saveAffiliationToDB(this , owner,
2135: null, MUCRole.Affiliation.owner,
2136: MUCRole.Affiliation.none);
2137: }
2138: // Save the existing room admins to the DB
2139: for (String admin : admins) {
2140: MUCPersistenceManager.saveAffiliationToDB(this , admin,
2141: null, MUCRole.Affiliation.admin,
2142: MUCRole.Affiliation.none);
2143: }
2144: // Save the existing room members to the DB
2145: for (String bareJID : members.keySet()) {
2146: MUCPersistenceManager.saveAffiliationToDB(this ,
2147: bareJID, members.get(bareJID),
2148: MUCRole.Affiliation.member,
2149: MUCRole.Affiliation.none);
2150: }
2151: // Save the existing room outcasts to the DB
2152: for (String outcast : outcasts) {
2153: MUCPersistenceManager.saveAffiliationToDB(this ,
2154: outcast, null, MUCRole.Affiliation.outcast,
2155: MUCRole.Affiliation.none);
2156: }
2157: }
2158: }
2159:
2160: public void writeExternal(ObjectOutput out) throws IOException {
2161: ExternalizableUtil.getInstance().writeSafeUTF(out, name);
2162: ExternalizableUtil.getInstance().writeLong(out, startTime);
2163: ExternalizableUtil.getInstance().writeLong(out, lockedTime);
2164: ExternalizableUtil.getInstance().writeStringList(out, owners);
2165: ExternalizableUtil.getInstance().writeStringList(out, admins);
2166: ExternalizableUtil.getInstance().writeStringMap(out, members);
2167: ExternalizableUtil.getInstance().writeStringList(out, outcasts);
2168: ExternalizableUtil.getInstance().writeSafeUTF(out,
2169: naturalLanguageName);
2170: ExternalizableUtil.getInstance().writeSafeUTF(out, description);
2171: ExternalizableUtil.getInstance().writeBoolean(out,
2172: canOccupantsChangeSubject);
2173: ExternalizableUtil.getInstance().writeInt(out, maxUsers);
2174: ExternalizableUtil.getInstance().writeStringList(out,
2175: rolesToBroadcastPresence);
2176: ExternalizableUtil.getInstance().writeBoolean(out, publicRoom);
2177: ExternalizableUtil.getInstance().writeBoolean(out, persistent);
2178: ExternalizableUtil.getInstance().writeBoolean(out, moderated);
2179: ExternalizableUtil.getInstance().writeBoolean(out, membersOnly);
2180: ExternalizableUtil.getInstance().writeBoolean(out,
2181: canOccupantsInvite);
2182: ExternalizableUtil.getInstance().writeSafeUTF(out, password);
2183: ExternalizableUtil.getInstance().writeBoolean(out,
2184: canAnyoneDiscoverJID);
2185: ExternalizableUtil.getInstance().writeBoolean(out, logEnabled);
2186: ExternalizableUtil.getInstance().writeBoolean(out,
2187: loginRestrictedToNickname);
2188: ExternalizableUtil.getInstance().writeBoolean(out,
2189: canChangeNickname);
2190: ExternalizableUtil.getInstance().writeBoolean(out,
2191: registrationEnabled);
2192: ExternalizableUtil.getInstance().writeSafeUTF(out, subject);
2193: ExternalizableUtil.getInstance().writeLong(out, roomID);
2194: ExternalizableUtil.getInstance().writeLong(out,
2195: creationDate.getTime());
2196: ExternalizableUtil.getInstance().writeLong(out,
2197: modificationDate.getTime());
2198: ExternalizableUtil.getInstance().writeBoolean(out,
2199: emptyDate != null);
2200: if (emptyDate != null) {
2201: ExternalizableUtil.getInstance().writeLong(out,
2202: emptyDate.getTime());
2203: }
2204: ExternalizableUtil.getInstance().writeBoolean(out, savedToDB);
2205: }
2206:
2207: public void readExternal(ObjectInput in) throws IOException,
2208: ClassNotFoundException {
2209: name = ExternalizableUtil.getInstance().readSafeUTF(in);
2210: startTime = ExternalizableUtil.getInstance().readLong(in);
2211: lockedTime = ExternalizableUtil.getInstance().readLong(in);
2212: owners.addAll(ExternalizableUtil.getInstance().readStringList(
2213: in));
2214: admins.addAll(ExternalizableUtil.getInstance().readStringList(
2215: in));
2216: members.putAll(ExternalizableUtil.getInstance().readStringMap(
2217: in));
2218: outcasts.addAll(ExternalizableUtil.getInstance()
2219: .readStringList(in));
2220: naturalLanguageName = ExternalizableUtil.getInstance()
2221: .readSafeUTF(in);
2222: description = ExternalizableUtil.getInstance().readSafeUTF(in);
2223: canOccupantsChangeSubject = ExternalizableUtil.getInstance()
2224: .readBoolean(in);
2225: maxUsers = ExternalizableUtil.getInstance().readInt(in);
2226: rolesToBroadcastPresence.addAll(ExternalizableUtil
2227: .getInstance().readStringList(in));
2228: publicRoom = ExternalizableUtil.getInstance().readBoolean(in);
2229: persistent = ExternalizableUtil.getInstance().readBoolean(in);
2230: moderated = ExternalizableUtil.getInstance().readBoolean(in);
2231: membersOnly = ExternalizableUtil.getInstance().readBoolean(in);
2232: canOccupantsInvite = ExternalizableUtil.getInstance()
2233: .readBoolean(in);
2234: password = ExternalizableUtil.getInstance().readSafeUTF(in);
2235: canAnyoneDiscoverJID = ExternalizableUtil.getInstance()
2236: .readBoolean(in);
2237: logEnabled = ExternalizableUtil.getInstance().readBoolean(in);
2238: loginRestrictedToNickname = ExternalizableUtil.getInstance()
2239: .readBoolean(in);
2240: canChangeNickname = ExternalizableUtil.getInstance()
2241: .readBoolean(in);
2242: registrationEnabled = ExternalizableUtil.getInstance()
2243: .readBoolean(in);
2244: subject = ExternalizableUtil.getInstance().readSafeUTF(in);
2245: roomID = ExternalizableUtil.getInstance().readLong(in);
2246: creationDate = new Date(ExternalizableUtil.getInstance()
2247: .readLong(in));
2248: modificationDate = new Date(ExternalizableUtil.getInstance()
2249: .readLong(in));
2250: if (ExternalizableUtil.getInstance().readBoolean(in)) {
2251: emptyDate = new Date(ExternalizableUtil.getInstance()
2252: .readLong(in));
2253: }
2254: savedToDB = ExternalizableUtil.getInstance().readBoolean(in);
2255:
2256: server = (MultiUserChatServerImpl) XMPPServer.getInstance()
2257: .getMultiUserChatServer();
2258: roomHistory = new MUCRoomHistory(this , new HistoryStrategy(
2259: server.getHistoryStrategy()));
2260:
2261: PacketRouter packetRouter = XMPPServer.getInstance()
2262: .getPacketRouter();
2263: this .iqOwnerHandler = new IQOwnerHandler(this , packetRouter);
2264: this .iqAdminHandler = new IQAdminHandler(this , packetRouter);
2265:
2266: server = (MultiUserChatServerImpl) XMPPServer.getInstance()
2267: .getMultiUserChatServer();
2268: router = packetRouter;
2269: }
2270:
2271: public void updateConfiguration(LocalMUCRoom otherRoom) {
2272: startTime = otherRoom.startTime;
2273: lockedTime = otherRoom.lockedTime;
2274: owners = otherRoom.owners;
2275: admins = otherRoom.admins;
2276: members = otherRoom.members;
2277: outcasts = otherRoom.outcasts;
2278: naturalLanguageName = otherRoom.naturalLanguageName;
2279: description = otherRoom.description;
2280: canOccupantsChangeSubject = otherRoom.canOccupantsChangeSubject;
2281: maxUsers = otherRoom.maxUsers;
2282: rolesToBroadcastPresence = otherRoom.rolesToBroadcastPresence;
2283: publicRoom = otherRoom.publicRoom;
2284: persistent = otherRoom.persistent;
2285: moderated = otherRoom.moderated;
2286: membersOnly = otherRoom.membersOnly;
2287: canOccupantsInvite = otherRoom.canOccupantsInvite;
2288: password = otherRoom.password;
2289: canAnyoneDiscoverJID = otherRoom.canAnyoneDiscoverJID;
2290: logEnabled = otherRoom.logEnabled;
2291: loginRestrictedToNickname = otherRoom.loginRestrictedToNickname;
2292: canChangeNickname = otherRoom.canChangeNickname;
2293: registrationEnabled = otherRoom.registrationEnabled;
2294: subject = otherRoom.subject;
2295: roomID = otherRoom.roomID;
2296: creationDate = otherRoom.creationDate;
2297: modificationDate = otherRoom.modificationDate;
2298: emptyDate = otherRoom.emptyDate;
2299: savedToDB = otherRoom.savedToDB;
2300: }
2301:
2302: /*
2303: * (non-Javadoc)
2304: * @see org.jivesoftware.util.resultsetmanager.Result#getUID()
2305: */
2306: public String getUID() {
2307: // name is unique for each one particular MUC service.
2308: return name;
2309: }
2310: }
|