Commit 3fb9610d authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Initial version. (MUC cluster support).

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@8788 b35dd754-fafc-0310-a699-88a17e54d16e
parent 52a517c6
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.Message;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that broadcasts a message to local room occupants. When a room occupant sends a
* message to the room each cluster node will execute this task and broadcast the message
* to its local room occupants.
*
* @author Gaston Dombiak
*/
public class BroascastMessageRequest extends MUCRoomTask {
private int occupants;
private Message message;
public BroascastMessageRequest() {
}
public BroascastMessageRequest(LocalMUCRoom room, Message message, int occupants) {
super(room);
this.message = message;
this.occupants = occupants;
}
public Message getMessage() {
return message;
}
public int getOccupants() {
return occupants;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().broadcast(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) message.getElement());
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
message = new Message(packetElement, true);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.Presence;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that broadcasts the presence of a room occupant to the occupants of the room
* being hosted by the cluster node. When a room occupant changes his presence an
* instance of this class will be sent to each cluster node and when executed a broadcast
* of the updated presence will be sent to local room occupants.
*
* @author Gaston Dombiak
*/
public class BroascastPresenceRequest extends MUCRoomTask {
private Presence presence;
public BroascastPresenceRequest() {
}
public BroascastPresenceRequest(LocalMUCRoom room, Presence message) {
super(room);
this.presence = message;
}
public Presence getPresence() {
return presence;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().broadcast(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
presence = new Presence(packetElement, true);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.Presence;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that changes the nickname of an existing room occupant in the cluster node. When
* a room occupant changes his nickname the other cluster nodes, that hold a
* {@link org.jivesoftware.openfire.muc.spi.RemoteMUCRole} will need to update their local
* information with the new nickname.
*
* @author Gaston Dombiak
*/
public class ChangeNickname extends MUCRoomTask {
private String oldNick;
private String newNick;
private Presence presence;
public ChangeNickname() {
}
public ChangeNickname(LocalMUCRoom room, String oldNick, String newNick, Presence presence) {
super(room);
this.oldNick = oldNick;
this.newNick = newNick;
this.presence = presence;
}
public String getOldNick() {
return oldNick;
}
public String getNewNick() {
return newNick;
}
public Presence getPresence() {
return presence;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().nicknameChanged(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
ExternalizableUtil.getInstance().writeSafeUTF(out, oldNick);
ExternalizableUtil.getInstance().writeSafeUTF(out, newNick);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
presence = new Presence(packetElement, true);
oldNick = ExternalizableUtil.getInstance().readSafeUTF(in);
newNick = ExternalizableUtil.getInstance().readSafeUTF(in);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that destroys the local room in the cluster node. Local room occupants
* hosted in the cluster node will get the notification of the room being
* destroyed.
*
* @author Gaston Dombiak
*/
public class DestroyRoomRequest extends MUCRoomTask {
private String alternateJID;
private String reason;
public DestroyRoomRequest() {
}
public DestroyRoomRequest(LocalMUCRoom room, String alternateJID, String reason) {
super(room);
this.alternateJID = alternateJID;
this.reason = reason;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().destroyRoom(this);
}
public String getAlternateJID() {
return alternateJID;
}
public String getReason() {
return reason;
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeBoolean(out, alternateJID != null);
if (alternateJID != null) {
ExternalizableUtil.getInstance().writeSafeUTF(out, alternateJID);
}
ExternalizableUtil.getInstance().writeBoolean(out, reason != null);
if (reason != null) {
ExternalizableUtil.getInstance().writeSafeUTF(out, reason);
}
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
if (ExternalizableUtil.getInstance().readBoolean(in)) {
alternateJID = ExternalizableUtil.getInstance().readSafeUTF(in);
}
if (ExternalizableUtil.getInstance().readBoolean(in)) {
reason = ExternalizableUtil.getInstance().readSafeUTF(in);
}
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ClusterTask;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Task requested by each cluster node when a new node joins the cluster. Each existing cluster
* node will request the list of rooms with occupants <tt>hosted by</tt> the new cluster node.
*
* @author Gaston Dombiak
*/
public class GetNewMemberRoomsRequest implements ClusterTask {
private List<RoomInfo> rooms;
public GetNewMemberRoomsRequest() {
}
public Object getResult() {
return rooms;
}
public void run() {
rooms = new ArrayList<RoomInfo>();
// Get rooms that have local occupants and include them in the reply
MultiUserChatServer mucServer = XMPPServer.getInstance().getMultiUserChatServer();
for (MUCRoom room : mucServer.getChatRooms()) {
LocalMUCRoom localRoom = (LocalMUCRoom) room;
Collection<MUCRole> localOccupants = new ArrayList<MUCRole>();
for (MUCRole occupant : room.getOccupants()) {
if (occupant.isLocal()) {
localOccupants.add(occupant);
}
}
if (!localOccupants.isEmpty()) {
rooms.add(new RoomInfo(localRoom, localOccupants));
}
}
}
public void writeExternal(ObjectOutput out) throws IOException {
// Do nothing
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// Do nothing
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.spi.MultiUserChatServerImpl;
import org.jivesoftware.util.cache.ClusterTask;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task to be executed in each cluster node to obtain the total number of
* users using the multi user chat service.
*
* @author Gaston Dombiak
*/
public class GetNumberConnectedUsers implements ClusterTask{
private Integer count;
public Object getResult() {
return count;
}
public void run() {
MultiUserChatServerImpl mucServer = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
count = mucServer.getNumberConnectedUsers(true);
}
public void writeExternal(ObjectOutput out) throws IOException {
// Do nothing
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// Do nothing
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ClusterTask;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task related to a room to be executed in a cluster node. This is a base
* class to specific room tasks. The base class just keeps track of the room
* related to the task.
*
* @author Gaston Dombiak
*/
public abstract class MUCRoomTask implements ClusterTask {
private boolean originator;
private LocalMUCRoom room;
protected MUCRoomTask() {
}
protected MUCRoomTask(LocalMUCRoom room) {
this.room = room;
}
public LocalMUCRoom getRoom() {
return room;
}
public boolean isOriginator() {
return originator;
}
public void setOriginator(boolean originator) {
this.originator = originator;
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeBoolean(out, originator);
ExternalizableUtil.getInstance().writeSafeUTF(out, room.getName());
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
originator = ExternalizableUtil.getInstance().readBoolean(in);
String roomName = ExternalizableUtil.getInstance().readSafeUTF(in);
room = (LocalMUCRoom) XMPPServer.getInstance().getMultiUserChatServer().getChatRoom(roomName);
if (room == null) {
throw new IllegalArgumentException("Room not found: " + roomName);
}
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.cluster.NodeID;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.JID;
import org.xmpp.packet.Presence;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that will remove a room occupant from the list of occupants in the room.
*
* @author Gaston Dombiak
*/
public class OccupantAddedEvent extends MUCRoomTask {
private Presence presence;
private int role;
private int affiliation;
private boolean voiceOnly;
private JID roleAddress;
private JID userAddress;
private NodeID nodeID;
private boolean sendPresence;
public OccupantAddedEvent() {
}
public OccupantAddedEvent(LocalMUCRoom room, MUCRole occupant) {
super(room);
presence = occupant.getPresence();
role = occupant.getRole().ordinal();
affiliation = occupant.getAffiliation().ordinal();
voiceOnly = occupant.isVoiceOnly();
roleAddress = occupant.getRoleAddress();
userAddress = occupant.getUserAddress();
nodeID = XMPPServer.getInstance().getNodeID();
}
public Presence getPresence() {
return presence;
}
public String getNickname() {
return presence.getTo().getResource();
}
public MUCRole.Role getRole() {
return MUCRole.Role.values()[role];
}
public MUCRole.Affiliation getAffiliation() {
return MUCRole.Affiliation.values()[affiliation];
}
public boolean isVoiceOnly() {
return voiceOnly;
}
public JID getRoleAddress() {
return roleAddress;
}
public JID getUserAddress() {
return userAddress;
}
public NodeID getNodeID() {
return nodeID;
}
/**
* Sets if the room should broadcast presence of the new occupant to occupants
* hosted by this cluster node.
*
* @param sendPresence true if the room should broadcast presence of the new occupant to occupants
* hosted by this cluster node.
*/
public void setSendPresence(boolean sendPresence) {
this.sendPresence = sendPresence;
}
/**
* Returns true if the room should broadcast presence of the new occupant to occupants
* hosted by this cluster node.
*
* @return true if the room should broadcast presence of the new occupant to occupants
* hosted by this cluster node.
*/
public boolean isSendPresence() {
return sendPresence;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().occupantAdded(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
ExternalizableUtil.getInstance().writeInt(out, role);
ExternalizableUtil.getInstance().writeInt(out, affiliation);
ExternalizableUtil.getInstance().writeBoolean(out, voiceOnly);
ExternalizableUtil.getInstance().writeSerializable(out, roleAddress);
ExternalizableUtil.getInstance().writeSerializable(out, userAddress);
ExternalizableUtil.getInstance().writeByteArray(out, nodeID.toByteArray());
ExternalizableUtil.getInstance().writeBoolean(out, sendPresence);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
presence = new Presence(packetElement, true);
role = ExternalizableUtil.getInstance().readInt(in);
affiliation = ExternalizableUtil.getInstance().readInt(in);
voiceOnly = ExternalizableUtil.getInstance().readBoolean(in);
roleAddress = (JID) ExternalizableUtil.getInstance().readSerializable(in);
userAddress = (JID) ExternalizableUtil.getInstance().readSerializable(in);
nodeID = NodeID.getInstance(ExternalizableUtil.getInstance().readByteArray(in));
sendPresence = ExternalizableUtil.getInstance().readBoolean(in);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that removes a room occupant from the list of occupants in the room. The
* occupant to remove is actualy a {@link org.jivesoftware.openfire.muc.spi.RemoteMUCRole}.
*
* @author Gaston Dombiak
*/
public class OccupantLeftEvent extends MUCRoomTask {
private MUCRole role;
private String nickname;
public OccupantLeftEvent() {
}
public OccupantLeftEvent(LocalMUCRoom room, MUCRole role) {
super(room);
this.role = role;
this.nickname = role.getNickname();
}
public MUCRole getRole() {
if (role == null) {
try {
role = getRoom().getOccupant(nickname);
} catch (UserNotFoundException e) {
// Ignore
}
}
return role;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().leaveRoom(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSafeUTF(out, nickname);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
nickname = ExternalizableUtil.getInstance().readSafeUTF(in);
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.openfire.muc.spi.MultiUserChatServerImpl;
import org.jivesoftware.util.cache.ClusterTask;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that adds a new local room to the cluster node. When a room is created in a
* cluster node the rest of the cluster nodes will need to get the new room added
* in their list of existing rooms.
*
* @author Gaston Dombiak
*/
public class RoomAvailableEvent implements ClusterTask {
private LocalMUCRoom room;
public RoomAvailableEvent() {
}
public RoomAvailableEvent(LocalMUCRoom room) {
this.room = room;
}
public Object getResult() {
return null;
}
public void run() {
MultiUserChatServerImpl mucServer = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
mucServer.chatRoomAdded(room);
}
public void writeExternal(ObjectOutput out) throws IOException {
room.writeExternal(out);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
room = new LocalMUCRoom();
room.readExternal(in);
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Representation of a room configuration and its occupants. This information is requested
* by a cluster node when joining the cluster (requested to the senior member) and also from
* each cluster node to the new node joining the cluster. Each cluster node (existing and
* new one) has to merge its local rooms with the new ones.
*
* @author Gaston Dombiak
*/
public class RoomInfo implements Externalizable {
private LocalMUCRoom room;
private List<OccupantAddedEvent> occupants = new ArrayList<OccupantAddedEvent>();
/**
* Do not use this constructor. Needed for Externalizable interface.
*/
public RoomInfo() {
}
public RoomInfo(LocalMUCRoom room, Collection<MUCRole> occupants) {
this.room = room;
for (MUCRole occupant : occupants) {
this.occupants.add(new OccupantAddedEvent(room, occupant));
}
}
public LocalMUCRoom getRoom() {
return room;
}
public List<OccupantAddedEvent> getOccupants() {
return occupants;
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeSerializable(out, room);
ExternalizableUtil.getInstance().writeExternalizableCollection(out, occupants);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
room = (LocalMUCRoom) ExternalizableUtil.getInstance().readSerializable(in);
ExternalizableUtil.getInstance().readExternalizableCollection(in, occupants, getClass().getClassLoader());
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.spi.MultiUserChatServerImpl;
import org.jivesoftware.util.cache.ClusterTask;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that will remove a local room from the cluster node. When a room is destroyed
* in a cluster node the rest of the cluster nodes will need to destroy their copy
* and send notifications to the room occupants hosted in the local cluster node.
*
* @author Gaston Dombiak
*/
public class RoomRemovedEvent implements ClusterTask {
private String roomName;
public RoomRemovedEvent() {
}
public RoomRemovedEvent(String roomName) {
this.roomName = roomName;
}
public Object getResult() {
return null;
}
public void run() {
MultiUserChatServerImpl mucServer = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
mucServer.chatRoomRemoved(roomName);
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeSafeUTF(out, roomName);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
roomName = ExternalizableUtil.getInstance().readSafeUTF(in);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that updates the configuration of a local room. When a room gets updated in a
* cluster node the rest of the cluster nodes will need to update their copy of the
* local room.
*
* @author Gaston Dombiak
*/
public class RoomUpdatedEvent extends MUCRoomTask {
private LocalMUCRoom room;
public RoomUpdatedEvent() {
}
public RoomUpdatedEvent(LocalMUCRoom room) {
super(room);
this.room = room;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().updateConfiguration(room);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
room.writeExternal(out);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
room = new LocalMUCRoom();
room.readExternal(in);
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ClusterTask;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.List;
/**
* Task to be requested by a node that joins a cluster and be executed in the senior cluster member to get
* the rooms with occupants. The list of rooms with occupants is returned to the new cluster node so that
* the new cluster node can be updated and have the same information shared by the cluster.<p>
*
* Moreover, each existing cluster node will also need to learn the rooms with occupants that exist in
* the new cluster node and replicate them. This work is accomplished using {@link GetNewMemberRoomsRequest}.
*
* @author Gaston Dombiak
*/
public class SeniorMemberRoomsRequest implements ClusterTask {
private List<RoomInfo> rooms;
public SeniorMemberRoomsRequest() {
}
public Object getResult() {
return rooms;
}
public void run() {
rooms = new ArrayList<RoomInfo>();
// Get rooms that have occupants and include them in the reply
MultiUserChatServer mucServer = XMPPServer.getInstance().getMultiUserChatServer();
for (MUCRoom room : mucServer.getChatRooms()) {
LocalMUCRoom localRoom = (LocalMUCRoom) room;
if (!room.getOccupants().isEmpty()) {
rooms.add(new RoomInfo(localRoom, localRoom.getOccupants()));
}
}
}
public void writeExternal(ObjectOutput out) throws IOException {
// Do nothing
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// Do nothing
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.muc.HistoryStrategy;
import org.jivesoftware.openfire.muc.spi.MultiUserChatServerImpl;
import org.jivesoftware.util.cache.ClusterTask;
import org.jivesoftware.util.cache.ExternalizableUtil;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Cluster task that will update the history strategy used by the MultiUserChatServer
* service. It is currently not possible to edit the history strategy of a given room
* but only of the service. Therefore, this task will only update the service's strategy.
*
* @author Gaston Dombiak
*/
public class UpdateHistoryStrategy implements ClusterTask {
private int type;
private int maxNumber;
public UpdateHistoryStrategy() {
}
public UpdateHistoryStrategy(HistoryStrategy historyStrategy) {
type = historyStrategy.getType().ordinal();
maxNumber = historyStrategy.getMaxNumber();
}
public Object getResult() {
return null;
}
public void run() {
MultiUserChatServerImpl mucServer = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
HistoryStrategy strategy = mucServer.getHistoryStrategy();
strategy.setType(HistoryStrategy.Type.values()[type]);
strategy.setMaxNumber(maxNumber);
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeInt(out, type);
ExternalizableUtil.getInstance().writeInt(out, maxNumber);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
type = ExternalizableUtil.getInstance().readInt(in);
maxNumber = ExternalizableUtil.getInstance().readInt(in);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.Presence;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that updates all information regarding a room occupant. Whenever a room
* occupant gets his affiliation, role, nickname or presence updated the other
* cluster nodes will need to reflect these changes.
*
* @author Gaston Dombiak
*/
public class UpdateOccupant extends MUCRoomTask {
private Presence presence;
private String nickname;
private int role;
private int affiliation;
public UpdateOccupant() {
}
public UpdateOccupant(LocalMUCRoom room, MUCRole role) {
super(room);
this.presence = role.getPresence();
this.nickname = role.getNickname();
this.role = role.getRole().ordinal();
this.affiliation = role.getAffiliation().ordinal();
}
public Presence getPresence() {
return presence;
}
public String getNickname() {
return nickname;
}
public MUCRole.Role getRole() {
return MUCRole.Role.values()[role];
}
public MUCRole.Affiliation getAffiliation() {
return MUCRole.Affiliation.values()[affiliation];
}
public Object getResult() {
return null;
}
public void run() {
getRoom().occupantUpdated(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
ExternalizableUtil.getInstance().writeSafeUTF(out, nickname);
ExternalizableUtil.getInstance().writeInt(out, role);
ExternalizableUtil.getInstance().writeInt(out, affiliation);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
presence = new Presence(packetElement, true);
nickname = ExternalizableUtil.getInstance().readSafeUTF(in);
role = ExternalizableUtil.getInstance().readInt(in);
affiliation = ExternalizableUtil.getInstance().readInt(in);
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.cluster;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.muc.spi.LocalMUCRoom;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.Presence;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Task that updates the presence of an occupant in a room. Each time an occupant
* changes his presence in the room the other cluster nodes will need to get the
* presence updated too for the occupant.
*
* @author Gaston Dombiak
*/
public class UpdatePresence extends MUCRoomTask {
private Presence presence;
private String nickname;
public UpdatePresence() {
}
public UpdatePresence(LocalMUCRoom room, Presence presence, String nickname) {
super(room);
this.presence = presence;
this.nickname = nickname;
}
public Presence getPresence() {
return presence;
}
public String getNickname() {
return nickname;
}
public Object getResult() {
return null;
}
public void run() {
getRoom().presenceUpdated(this);
}
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
ExternalizableUtil.getInstance().writeSafeUTF(out, nickname);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
Element packetElement = (Element) ExternalizableUtil.getInstance().readSerializable(in);
presence = new Presence(packetElement, true);
nickname = ExternalizableUtil.getInstance().readSafeUTF(in);
}
}
/**
* $RCSfile: LocalMUCRole.java,v $
* $Revision: 3168 $
* $Date: 2005-12-07 13:55:47 -0300 (Wed, 07 Dec 2005) $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.spi;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.cluster.NodeID;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.openfire.muc.NotAllowedException;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.ElementUtil;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence;
/**
* Implementation of a local room occupant.
*
* @author Gaston Dombiak
*/
public class LocalMUCRole implements MUCRole {
/**
* The room this role is valid in.
*/
private LocalMUCRoom room;
/**
* The user of the role.
*/
private LocalMUCUser user;
/**
* The user's nickname in the room.
*/
private String nick;
/**
* The user's presence in the room.
*/
private Presence presence;
/**
* The chatserver that hosts this role.
*/
private MultiUserChatServer server;
/**
* The role.
*/
private MUCRole.Role role;
/**
* The affiliation.
*/
private MUCRole.Affiliation affiliation;
/**
* Flag that indicates if the room occupant is in the room only to send messages or also
* to receive room messages. True means that the room occupant is deaf.
*/
private boolean voiceOnly = false;
/**
* The router used to send packets from this role.
*/
private PacketRouter router;
/**
* The address of the person masquerading in this role.
*/
private JID rJID;
/**
* A fragment containing the x-extension for non-anonymous rooms.
*/
private Element extendedInformation;
/**
* Cache session of local user that is a room occupant. If the room occupant is not a local
* user then nothing will be cached and packets will be sent through the PacketRouter.
*/
private ClientSession session;
/**
* Create a new role.
*
* @param chatserver the server hosting the role.
* @param chatroom the room the role is valid in.
* @param nickname the nickname of the user in the role.
* @param role the role of the user in the room.
* @param affiliation the affiliation of the user in the room.
* @param chatuser the user on the chat server.
* @param presence the presence sent by the user to join the room.
* @param packetRouter the packet router for sending messages from this role.
*/
public LocalMUCRole(MultiUserChatServer chatserver, LocalMUCRoom chatroom, String nickname,
MUCRole.Role role, MUCRole.Affiliation affiliation, LocalMUCUser chatuser, Presence presence,
PacketRouter packetRouter)
{
this.room = chatroom;
this.nick = nickname;
this.user = chatuser;
this.server = chatserver;
this.router = packetRouter;
this.role = role;
this.affiliation = affiliation;
// Cache the user's session (will only work for local users)
this.session = XMPPServer.getInstance().getSessionManager().getSession(presence.getFrom());
extendedInformation =
DocumentHelper.createElement(QName.get("x", "http://jabber.org/protocol/muc#user"));
calculateExtendedInformation();
rJID = new JID(room.getName(), server.getServiceDomain(), nick);
setPresence(presence);
// Check if new occupant wants to be a deaf occupant
Element element = presence.getElement()
.element(QName.get("x", "http://jivesoftware.org/protocol/muc"));
if (element != null) {
voiceOnly = element.element("deaf-occupant") != null;
}
// Add the new role to the list of roles
user.addRole(room.getName(), this);
}
public Presence getPresence() {
return presence;
}
public void setPresence(Presence newPresence) {
// Try to remove the element whose namespace is "http://jabber.org/protocol/muc" since we
// don't need to include that element in future presence broadcasts
Element element = newPresence.getElement().element(QName.get("x", "http://jabber.org/protocol/muc"));
if (element != null) {
newPresence.getElement().remove(element);
}
this.presence = newPresence;
this.presence.setFrom(getRoleAddress());
if (extendedInformation != null) {
extendedInformation.setParent(null);
presence.getElement().add(extendedInformation);
}
}
public void setRole(MUCRole.Role newRole) throws NotAllowedException {
// Don't allow to change the role to an owner or admin unless the new role is moderator
if (MUCRole.Affiliation.owner == affiliation || MUCRole.Affiliation.admin == affiliation) {
if (MUCRole.Role.moderator != newRole) {
throw new NotAllowedException();
}
}
// A moderator cannot be kicked from a room
if (MUCRole.Role.moderator == role && MUCRole.Role.none == newRole) {
throw new NotAllowedException();
}
// TODO A moderator MUST NOT be able to revoke voice from a user whose affiliation is at or
// TODO above the moderator's level.
role = newRole;
if (MUCRole.Role.none == role) {
presence.setType(Presence.Type.unavailable);
presence.setStatus(null);
}
calculateExtendedInformation();
}
public MUCRole.Role getRole() {
return role;
}
public void setAffiliation(MUCRole.Affiliation newAffiliation) throws NotAllowedException {
// Don't allow to ban an owner or an admin
if (MUCRole.Affiliation.owner == affiliation || MUCRole.Affiliation.admin== affiliation) {
if (MUCRole.Affiliation.outcast == newAffiliation) {
throw new NotAllowedException();
}
}
affiliation = newAffiliation;
// TODO The fragment is being calculated twice (1. setting the role & 2. setting the aff)
calculateExtendedInformation();
}
public MUCRole.Affiliation getAffiliation() {
return affiliation;
}
public String getNickname() {
return nick;
}
public void changeNickname(String nickname) {
this.nick = nickname;
setRoleAddress(new JID(room.getName(), server.getServiceDomain(), nick));
}
public void destroy() {
// Notify the user that he/she is no longer in the room
user.removeRole(room.getName());
}
public MUCRoom getChatRoom() {
return room;
}
public JID getRoleAddress() {
return rJID;
}
public JID getUserAddress() {
return user.getAddress();
}
public boolean isLocal() {
return true;
}
public NodeID getNodeID() {
return XMPPServer.getInstance().getNodeID();
}
private void setRoleAddress(JID jid) {
rJID = jid;
// Set the new sender of the user presence in the room
presence.setFrom(jid);
}
public boolean isVoiceOnly() {
return voiceOnly;
}
public void send(Packet packet) {
if (packet == null) {
return;
}
packet.setTo(user.getAddress());
if (session != null && session.getStatus() == Session.STATUS_AUTHENTICATED) {
// Send the packet directly to the local user session
session.process(packet);
}
else {
router.route(packet);
}
}
/**
* Calculates and sets the extended presence information to add to the presence.
* The information to add contains the user's jid, affiliation and role.
*/
private void calculateExtendedInformation() {
ElementUtil.setProperty(extendedInformation, "x.item:jid", user.getAddress().toString());
ElementUtil.setProperty(extendedInformation, "x.item:affiliation", affiliation.toString());
ElementUtil.setProperty(extendedInformation, "x.item:role", role.toString());
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 3158 $
* $Date: 2005-12-04 22:55:49 -0300 (Sun, 04 Dec 2005) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.spi;
import org.dom4j.Element;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.cluster.NodeID;
import org.jivesoftware.openfire.muc.*;
import org.jivesoftware.openfire.muc.cluster.*;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.CacheFactory;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.*;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Implementation of a chatroom that is being hosted by this JVM. A LocalMUCRoom could represent
* a persistent room which means that its configuration will be maintained in synch with its
* representation in the database.<p>
*
* When running in a cluster each cluster node will have its own copy of local rooms. Persistent
* rooms will be loaded by each cluster node when starting up. Not persistent rooms will be copied
* from the senior cluster member. All room occupants will be copied from the senior cluster member
* too.
*
* @author Gaston Dombiak
*/
public class LocalMUCRoom implements MUCRoom {
/**
* The server hosting the room.
*/
private MultiUserChatServerImpl server;
/**
* The occupants of the room accessible by the occupants nickname.
*/
private Map<String,MUCRole> occupants = new ConcurrentHashMap<String, MUCRole>();
/**
* The occupants of the room accessible by the occupants bare JID.
*/
private Map<String, List<MUCRole>> occupantsByBareJID = new ConcurrentHashMap<String, List<MUCRole>>();
/**
* The occupants of the room accessible by the occupants full JID.
*/
private Map<JID, MUCRole> occupantsByFullJID = new ConcurrentHashMap<JID, MUCRole>();
/**
* The name of the room.
*/
private String name;
/**
* A lock to protect the room occupants.
*/
ReadWriteLock lock = new ReentrantReadWriteLock();
/**
* The role of the room itself.
*/
private MUCRole role = new RoomRole(this);
/**
* The router used to send packets for the room.
*/
private PacketRouter router;
/**
* The start time of the chat.
*/
long startTime;
/**
* The end time of the chat.
*/
long endTime;
/**
* After a room has been destroyed it may remain in memory but it won't be possible to use it.
* When a room is destroyed it is immediately removed from the MultiUserChatServer but it's
* possible that while the room was being destroyed it was being used by another thread so we
* need to protect the room under these rare circumstances.
*/
boolean isDestroyed = false;
/**
* ChatRoomHistory object.
*/
private MUCRoomHistory roomHistory;
/**
* Time when the room was locked. A value of zero means that the room is unlocked.
*/
private long lockedTime;
/**
* List of chatroom's owner. The list contains only bare jid.
*/
List<String> owners = new CopyOnWriteArrayList<String>();
/**
* List of chatroom's admin. The list contains only bare jid.
*/
List<String> admins = new CopyOnWriteArrayList<String>();
/**
* List of chatroom's members. The list contains only bare jid.
*/
private Map<String, String> members = new ConcurrentHashMap<String,String>();
/**
* List of chatroom's outcast. The list contains only bare jid of not allowed users.
*/
private List<String> outcasts = new CopyOnWriteArrayList<String>();
/**
* The natural language name of the room.
*/
private String naturalLanguageName;
/**
* Description of the room. The owner can change the description using the room configuration
* form.
*/
private String description;
/**
* Indicates if occupants are allowed to change the subject of the room.
*/
private boolean canOccupantsChangeSubject = JiveGlobals.getBooleanProperty("muc.room.canOccupantsChangeSubject", false);
/**
* Maximum number of occupants that could be present in the room. If the limit's been reached
* and a user tries to join, a not-allowed error will be returned.
*/
private int maxUsers = 30;
/**
* List of roles of which presence will be broadcasted to the rest of the occupants. This
* feature is useful for implementing "invisible" occupants.
*/
private List<String> rolesToBroadcastPresence = new ArrayList<String>();
/**
* A public room means that the room is searchable and visible. This means that the room can be
* located using disco requests.
*/
private boolean publicRoom = JiveGlobals.getBooleanProperty("muc.room.publicRoom", true);
/**
* Persistent rooms are saved to the database to make sure that rooms configurations can be
* restored in case the server goes down.
*/
private boolean persistent = JiveGlobals.getBooleanProperty("muc.room.persistent", false);
/**
* Moderated rooms enable only participants to speak. Users that join the room and aren't
* participants can't speak (they are just visitors).
*/
private boolean moderated = JiveGlobals.getBooleanProperty("muc.room.moderated", false);
/**
* A room is considered members-only if an invitation is required in order to enter the room.
* Any user that is not a member of the room won't be able to join the room unless the user
* decides to register with the room (thus becoming a member).
*/
private boolean membersOnly = JiveGlobals.getBooleanProperty("muc.room.membersOnly", false);
/**
* Some rooms may restrict the occupants that are able to send invitations. Sending an
* invitation in a members-only room adds the invitee to the members list.
*/
private boolean canOccupantsInvite = JiveGlobals.getBooleanProperty("muc.room.canOccupantsInvite", false);
/**
* The password that every occupant should provide in order to enter the room.
*/
private String password = null;
/**
* Every presence packet can include the JID of every occupant unless the owner deactives this
* configuration.
*/
private boolean canAnyoneDiscoverJID = JiveGlobals.getBooleanProperty("muc.room.canAnyoneDiscoverJID", true);
/**
* Enables the logging of the conversation. The conversation in the room will be saved to the
* database.
*/
private boolean logEnabled = JiveGlobals.getBooleanProperty("muc.room.logEnabled", false);
/**
* Enables the logging of the conversation. The conversation in the room will be saved to the
* database.
*/
private boolean loginRestrictedToNickname = JiveGlobals.getBooleanProperty("muc.room.loginRestrictedToNickname", false);
/**
* Enables the logging of the conversation. The conversation in the room will be saved to the
* database.
*/
private boolean canChangeNickname = JiveGlobals.getBooleanProperty("muc.room.canChangeNickname", true);
/**
* Enables the logging of the conversation. The conversation in the room will be saved to the
* database.
*/
private boolean registrationEnabled = JiveGlobals.getBooleanProperty("muc.room.registrationEnabled", true);
/**
* Internal component that handles IQ packets sent by the room owners.
*/
private IQOwnerHandler iqOwnerHandler;
/**
* Internal component that handles IQ packets sent by moderators, admins and owners.
*/
private IQAdminHandler iqAdminHandler;
/**
* The last known subject of the room. This information is used to respond disco requests. The
* MUCRoomHistory class holds the history of the room together with the last message that set
* the room's subject.
*/
private String subject = "";
/**
* The ID of the room. If the room is temporary and does not log its conversation then the value
* will always be -1. Otherwise a value will be obtained from the database.
*/
private long roomID = -1;
/**
* The date when the room was created.
*/
private Date creationDate;
/**
* The last date when the room's configuration was modified.
*/
private Date modificationDate;
/**
* The date when the last occupant left the room. A null value means that there are occupants
* in the room at the moment.
*/
private Date emptyDate;
/**
* Indicates if the room is present in the database.
*/
private boolean savedToDB = false;
/**
* Do not use this constructor. It was added to implement the Externalizable
* interface required to work inside of a cluster.
*/
public LocalMUCRoom() {
}
/**
* Create a new chat room.
*
* @param chatserver the server hosting the room.
* @param roomname the name of the room.
* @param packetRouter the router for sending packets from the room.
*/
LocalMUCRoom(MultiUserChatServer chatserver, String roomname, PacketRouter packetRouter) {
this.server = (MultiUserChatServerImpl) chatserver;
this.name = roomname;
this.naturalLanguageName = roomname;
this.description = roomname;
this.router = packetRouter;
this.startTime = System.currentTimeMillis();
this.creationDate = new Date(startTime);
this.modificationDate = new Date(startTime);
this.emptyDate = new Date(startTime);
// TODO Allow to set the history strategy from the configuration form?
roomHistory = new MUCRoomHistory(this, new HistoryStrategy(server.getHistoryStrategy()));
this.iqOwnerHandler = new IQOwnerHandler(this, packetRouter);
this.iqAdminHandler = new IQAdminHandler(this, packetRouter);
// No one can join the room except the room's owner
this.lockedTime = startTime;
// Set the default roles for which presence is broadcast
rolesToBroadcastPresence.add("moderator");
rolesToBroadcastPresence.add("participant");
rolesToBroadcastPresence.add("visitor");
}
public String getName() {
return name;
}
public long getID() {
if (isPersistent() || isLogEnabled()) {
if (roomID == -1) {
roomID = SequenceManager.nextID(JiveConstants.MUC_ROOM);
}
}
return roomID;
}
public void setID(long roomID) {
this.roomID = roomID;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public Date getModificationDate() {
return modificationDate;
}
public void setModificationDate(Date modificationDate) {
this.modificationDate = modificationDate;
}
public void setEmptyDate(Date emptyDate) {
// Do nothing if old value is same as new value
if (this.emptyDate == emptyDate) {
return;
}
this.emptyDate = emptyDate;
MUCPersistenceManager.updateRoomEmptyDate(this);
}
public Date getEmptyDate() {
return this.emptyDate;
}
public MUCRole getRole() {
return role;
}
public MUCRole getOccupant(String nickname) throws UserNotFoundException {
if (nickname == null) {
throw new UserNotFoundException();
}
MUCRole role = occupants.get(nickname.toLowerCase());
if (role != null) {
return role;
}
throw new UserNotFoundException();
}
public List<MUCRole> getOccupantsByBareJID(String jid) throws UserNotFoundException {
List<MUCRole> roles = occupantsByBareJID.get(jid);
if (roles != null && !roles.isEmpty()) {
return Collections.unmodifiableList(roles);
}
throw new UserNotFoundException();
}
public MUCRole getOccupantByFullJID(JID jid) {
MUCRole role = occupantsByFullJID.get(jid);
if (role != null) {
return role;
}
return null;
}
public Collection<MUCRole> getOccupants() {
return Collections.unmodifiableCollection(occupants.values());
}
public int getOccupantsCount() {
return occupants.size();
}
public boolean hasOccupant(String nickname) {
return occupants.containsKey(nickname.toLowerCase());
}
public String getReservedNickname(String bareJID) {
String answer = members.get(bareJID);
if (answer == null || answer.trim().length() == 0) {
return null;
}
return answer;
}
public MUCRole.Affiliation getAffiliation(String bareJID) {
if (owners.contains(bareJID)) {
return MUCRole.Affiliation.owner;
}
else if (admins.contains(bareJID)) {
return MUCRole.Affiliation.admin;
}
else if (members.containsKey(bareJID)) {
return MUCRole.Affiliation.member;
}
else if (outcasts.contains(bareJID)) {
return MUCRole.Affiliation.outcast;
}
return MUCRole.Affiliation.none;
}
public LocalMUCRole joinRoom(String nickname, String password, HistoryRequest historyRequest,
LocalMUCUser user, Presence presence) throws UnauthorizedException,
UserAlreadyExistsException, RoomLockedException, ForbiddenException,
RegistrationRequiredException, ConflictException, ServiceUnavailableException,
NotAcceptableException {
LocalMUCRole joinRole = null;
lock.writeLock().lock();
try {
// If the room has a limit of max user then check if the limit has been reached
if (isDestroyed || (getMaxUsers() > 0 && getOccupantsCount() >= getMaxUsers())) {
throw new ServiceUnavailableException();
}
boolean isOwner = owners.contains(user.getAddress().toBareJID());
// If the room is locked and this user is not an owner raise a RoomLocked exception
if (isLocked()) {
if (!isOwner) {
throw new RoomLockedException();
}
}
// If the user is already in the room raise a UserAlreadyExists exception
if (occupants.containsKey(nickname.toLowerCase())) {
throw new UserAlreadyExistsException();
}
// If the room is password protected and the provided password is incorrect raise a
// Unauthorized exception
if (isPasswordProtected()) {
if (password == null || !password.equals(getPassword())) {
throw new UnauthorizedException();
}
}
// If another user attempts to join the room with a nickname reserved by the first user
// raise a ConflictException
if (members.containsValue(nickname)) {
if (!nickname.equals(members.get(user.getAddress().toBareJID()))) {
throw new ConflictException();
}
}
if (isLoginRestrictedToNickname()) {
String reservedNickname = members.get(user.getAddress().toBareJID());
if (reservedNickname != null && !nickname.equals(reservedNickname)) {
throw new NotAcceptableException();
}
}
// Set the corresponding role based on the user's affiliation
MUCRole.Role role;
MUCRole.Affiliation affiliation;
if (isOwner) {
// The user is an owner. Set the role and affiliation accordingly.
role = MUCRole.Role.moderator;
affiliation = MUCRole.Affiliation.owner;
}
else if (server.getSysadmins().contains(user.getAddress().toBareJID())) {
// The user is a system administrator of the MUC service. Treat him as an owner
// although he won't appear in the list of owners
role = MUCRole.Role.moderator;
affiliation = MUCRole.Affiliation.owner;
}
else if (admins.contains(user.getAddress().toBareJID())) {
// The user is an admin. Set the role and affiliation accordingly.
role = MUCRole.Role.moderator;
affiliation = MUCRole.Affiliation.admin;
}
else if (members.containsKey(user.getAddress().toBareJID())) {
// The user is a member. Set the role and affiliation accordingly.
role = MUCRole.Role.participant;
affiliation = MUCRole.Affiliation.member;
}
else if (outcasts.contains(user.getAddress().toBareJID())) {
// The user is an outcast. Raise a "Forbidden" exception.
throw new ForbiddenException();
}
else {
// The user has no affiliation (i.e. NONE). Set the role accordingly.
if (isMembersOnly()) {
// The room is members-only and the user is not a member. Raise a
// "Registration Required" exception.
throw new RegistrationRequiredException();
}
role = (isModerated() ? MUCRole.Role.visitor : MUCRole.Role.participant);
affiliation = MUCRole.Affiliation.none;
}
// Create a new role for this user in this room
joinRole = new LocalMUCRole(server, this, nickname, role, affiliation, user, presence, router);
// Add the new user as an occupant of this room
occupants.put(nickname.toLowerCase(), joinRole);
// Update the tables of occupants based on the bare and full JID
List<MUCRole> list = occupantsByBareJID.get(user.getAddress().toBareJID());
if (list == null) {
list = new ArrayList<MUCRole>();
occupantsByBareJID.put(user.getAddress().toBareJID(), list);
}
list.add(joinRole);
occupantsByFullJID.put(user.getAddress(), joinRole);
}
finally {
lock.writeLock().unlock();
}
// Notify other cluster nodes that a new occupant joined the room
CacheFactory.doClusterTask(new OccupantAddedEvent(this, joinRole));
// Send presence of existing occupants to new occupant
sendInitialPresences(joinRole);
// It is assumed that the room is new based on the fact that it's locked and
// that it was locked when it was created.
boolean isRoomNew = isLocked() && creationDate.getTime() == lockedTime;
try {
// Send the presence of this new occupant to existing occupants
Presence joinPresence = joinRole.getPresence().createCopy();
if (isRoomNew) {
Element frag = joinPresence.getChildElement("x", "http://jabber.org/protocol/muc#user");
frag.addElement("status").addAttribute("code", "201");
}
broadcastPresence(joinPresence);
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
// If the room has just been created send the "room locked until configuration is
// confirmed" message
if (isRoomNew) {
Message message = new Message();
message.setType(Message.Type.groupchat);
message.setBody(LocaleUtils.getLocalizedString("muc.new"));
message.setFrom(role.getRoleAddress());
joinRole.send(message);
}
else if (isLocked()) {
// Warn the owner that the room is locked but it's not new
Message message = new Message();
message.setType(Message.Type.groupchat);
message.setBody(LocaleUtils.getLocalizedString("muc.locked"));
message.setFrom(role.getRoleAddress());
joinRole.send(message);
}
else if (canAnyoneDiscoverJID()) {
// Warn the new occupant that the room is non-anonymous (i.e. his JID will be
// public)
Message message = new Message();
message.setType(Message.Type.groupchat);
message.setBody(LocaleUtils.getLocalizedString("muc.warnnonanonymous"));
message.setFrom(role.getRoleAddress());
Element frag = message.addChildElement("x", "http://jabber.org/protocol/muc#user");
frag.addElement("status").addAttribute("code", "100");
joinRole.send(message);
}
if (historyRequest == null) {
Iterator history = roomHistory.getMessageHistory();
while (history.hasNext()) {
joinRole.send((Message) history.next());
}
}
else {
historyRequest.sendHistory(joinRole, roomHistory);
}
// Update the date when the last occupant left the room
setEmptyDate(null);
// Fire event that occupant joined the room
server.fireOccupantJoined(getRole().getRoleAddress(), user.getAddress(), joinRole.getNickname());
return joinRole;
}
/**
* Sends presence of existing occupants to new occupant.
*
* @param joinRole the role of the new occupant in the room.
*/
private void sendInitialPresences(LocalMUCRole joinRole) {
for (MUCRole occupant : occupants.values()) {
if (occupant == joinRole) {
continue;
}
Presence occupantPresence = occupant.getPresence();
// Skip to the next occupant if we cannot send presence of this occupant
if (hasToCheckRoleToBroadcastPresence()) {
Element frag = occupantPresence.getChildElement("x",
"http://jabber.org/protocol/muc#user");
// Check if we can broadcast the presence for this role
if (!canBroadcastPresence(frag.element("item").attributeValue("role"))) {
continue;
}
}
// Don't include the occupant's JID if the room is semi-anon and the new occupant
// is not a moderator
if (!canAnyoneDiscoverJID() && MUCRole.Role.moderator != joinRole.getRole()) {
occupantPresence = occupantPresence.createCopy();
Element frag = occupantPresence.getChildElement("x",
"http://jabber.org/protocol/muc#user");
frag.element("item").addAttribute("jid", null);
}
joinRole.send(occupantPresence);
}
}
public void occupantAdded(OccupantAddedEvent event) {
// Do not add new occupant with one with same nickname already exists
if (occupants.containsKey(event.getNickname().toLowerCase())) {
// TODO Handle conflict of nicknames
return;
}
// Create a proxy for the occupant that joined the room from another cluster node
RemoteMUCRole joinRole = new RemoteMUCRole(server, event);
// Add the new user as an occupant of this room
occupants.put(event.getNickname().toLowerCase(), joinRole);
// Update the tables of occupants based on the bare and full JID
List<MUCRole> list = occupantsByBareJID.get(event.getUserAddress().toBareJID());
if (list == null) {
list = new ArrayList<MUCRole>();
occupantsByBareJID.put(event.getUserAddress().toBareJID(), list);
}
list.add(joinRole);
occupantsByFullJID.put(event.getUserAddress(), joinRole);
// Update the date when the last occupant left the room
setEmptyDate(null);
// Fire event that occupant joined the room
server.fireOccupantJoined(getRole().getRoleAddress(), event.getUserAddress(), joinRole.getNickname());
// Check if we need to send presences of the new occupant to occupants hosted by this JVM
if (event.isSendPresence()) {
for (MUCRole occupant : occupants.values()) {
if (occupant.isLocal()) {
occupant.send(event.getPresence().createCopy());
}
}
}
}
public void leaveRoom(MUCRole leaveRole) {
if (leaveRole.isLocal()) {
// Ask other cluster nodes to remove occupant from room
OccupantLeftEvent event = new OccupantLeftEvent(this, leaveRole);
CacheFactory.doClusterTask(event);
}
try {
Presence presence = leaveRole.getPresence().createCopy();
presence.setType(Presence.Type.unavailable);
presence.setStatus(null);
// Change (or add) presence information about roles and affiliations
Element childElement = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
if (childElement == null) {
childElement = presence.addChildElement("x", "http://jabber.org/protocol/muc#user");
}
Element item = childElement.element("item");
if (item == null) {
item = childElement.addElement("item");
}
item.addAttribute("role", "none");
// Inform the leaving user that he/she has left the room
leaveRole.send(presence);
// Inform the rest of the room occupants that the user has left the room
broadcastPresence(presence);
}
catch (Exception e) {
Log.error(e);
}
// Remove occupant from room and destroy room if empty and not persistent
OccupantLeftEvent event = new OccupantLeftEvent(this, leaveRole);
event.setOriginator(true);
event.run();
}
public void leaveRoom(OccupantLeftEvent event) {
MUCRole leaveRole = event.getRole();
if (leaveRole == null) {
return;
}
lock.writeLock().lock();
try {
// Removes the role from the room
removeOccupantRole(leaveRole);
// TODO Implement this: If the room owner becomes unavailable for any reason before
// submitting the form (e.g., a lost connection), the service will receive a presence
// stanza of type "unavailable" from the owner to the room@service/nick or room@service
// (or both). The service MUST then destroy the room, sending a presence stanza of type
// "unavailable" from the room to the owner including a <destroy/> element and reason
// (if provided) as defined under the "Destroying a Room" use case.
// Remove the room from the server only if there are no more occupants and the room is
// not persistent
if (occupants.isEmpty() && !isPersistent()) {
endTime = System.currentTimeMillis();
if (event.isOriginator()) {
server.removeChatRoom(name);
}
// Fire event that the room has been destroyed
server.fireRoomDestroyed(getRole().getRoleAddress());
}
if (occupants.isEmpty()) {
// Update the date when the last occupant left the room
setEmptyDate(new Date());
}
}
finally {
lock.writeLock().unlock();
}
}
/**
* Removes the role of the occupant from all the internal occupants collections. The role will
* also be removed from the user's roles.
*
* @param leaveRole the role to remove.
*/
private void removeOccupantRole(MUCRole leaveRole) {
occupants.remove(leaveRole.getNickname().toLowerCase());
JID userAddress = leaveRole.getUserAddress();
// Notify the user that he/she is no longer in the room
leaveRole.destroy();
// Update the tables of occupants based on the bare and full JID
List list = occupantsByBareJID.get(userAddress.toBareJID());
if (list != null) {
list.remove(leaveRole);
if (list.isEmpty()) {
occupantsByBareJID.remove(userAddress.toBareJID());
}
}
occupantsByFullJID.remove(userAddress);
// Fire event that occupant left the room
server.fireOccupantLeft(getRole().getRoleAddress(), userAddress);
}
public void destroyRoom(DestroyRoomRequest destroyRequest) {
String alternateJID = destroyRequest.getAlternateJID();
String reason = destroyRequest.getReason();
MUCRole leaveRole;
Collection<MUCRole> removedRoles = new ArrayList<MUCRole>();
lock.writeLock().lock();
try {
boolean hasRemoteOccupants = false;
// Remove each occupant
for (String nickname: occupants.keySet()) {
leaveRole = occupants.remove(nickname);
if (leaveRole != null) {
// Add the removed occupant to the list of removed occupants. We are keeping a
// list of removed occupants to process later outside of the lock.
if (leaveRole.isLocal()) {
removedRoles.add(leaveRole);
}
else {
hasRemoteOccupants = true;
}
removeOccupantRole(leaveRole);
}
}
endTime = System.currentTimeMillis();
// Set that the room has been destroyed
isDestroyed = true;
if (destroyRequest.isOriginator()) {
if (hasRemoteOccupants) {
// Ask other cluster nodes to remove occupants since room is being destroyed
CacheFactory.doClusterTask(new DestroyRoomRequest(this, alternateJID, reason));
}
// Removes the room from the list of rooms hosted in the server
server.removeChatRoom(name);
}
}
finally {
lock.writeLock().unlock();
}
// Send an unavailable presence to each removed occupant
for (MUCRole removedRole : removedRoles) {
try {
// Send a presence stanza of type "unavailable" to the occupant
Presence presence = createPresence(Presence.Type.unavailable);
presence.setFrom(removedRole.getRoleAddress());
// A fragment containing the x-extension for room destruction.
Element fragment = presence.addChildElement("x",
"http://jabber.org/protocol/muc#user");
Element item = fragment.addElement("item");
item.addAttribute("affiliation", "none");
item.addAttribute("role", "none");
if (alternateJID != null && alternateJID.length() > 0) {
fragment.addElement("destroy").addAttribute("jid", alternateJID);
}
if (reason != null && reason.length() > 0) {
Element destroy = fragment.element("destroy");
if (destroy == null) {
destroy = fragment.addElement("destroy");
}
destroy.addElement("reason").setText(reason);
}
removedRole.send(presence);
}
catch (Exception e) {
Log.error(e);
}
}
if (destroyRequest.isOriginator()) {
// Remove the room from the DB if the room was persistent
MUCPersistenceManager.deleteFromDB(this);
}
// Fire event that the room has been destroyed
server.fireRoomDestroyed(getRole().getRoleAddress());
}
public void destroyRoom(String alternateJID, String reason) {
DestroyRoomRequest destroyRequest = new DestroyRoomRequest(this, alternateJID, reason);
destroyRequest.setOriginator(true);
destroyRequest.run();
}
public Presence createPresence(Presence.Type presenceType) throws UnauthorizedException {
Presence presence = new Presence();
presence.setType(presenceType);
presence.setFrom(role.getRoleAddress());
return presence;
}
public void serverBroadcast(String msg) {
Message message = new Message();
message.setType(Message.Type.groupchat);
message.setBody(msg);
message.setFrom(role.getRoleAddress());
broadcast(message);
}
public void sendPublicMessage(Message message, MUCRole senderRole) throws ForbiddenException {
// Check that if the room is moderated then the sender of the message has to have voice
if (isModerated() && senderRole.getRole().compareTo(MUCRole.Role.participant) > 0) {
throw new ForbiddenException();
}
// Send the message to all occupants
message.setFrom(senderRole.getRoleAddress());
send(message);
// Fire event that message was receibed by the room
server.fireMessageReceived(getRole().getRoleAddress(), senderRole.getUserAddress(),
senderRole.getNickname(), message);
}
public void sendPrivatePacket(Packet packet, MUCRole senderRole) throws NotFoundException {
String resource = packet.getTo().getResource();
MUCRole occupant = occupants.get(resource.toLowerCase());
if (occupant != null) {
packet.setFrom(senderRole.getRoleAddress());
occupant.send(packet);
}
else {
throw new NotFoundException();
}
}
public void send(Packet packet) {
if (packet instanceof Message) {
broadcast((Message)packet);
}
else if (packet instanceof Presence) {
broadcastPresence((Presence)packet);
}
else if (packet instanceof IQ) {
IQ reply = IQ.createResultIQ((IQ) packet);
reply.setChildElement(((IQ) packet).getChildElement());
reply.setError(PacketError.Condition.bad_request);
router.route(reply);
}
}
/**
* Broadcasts the specified presence to all room occupants. If the presence belongs to a
* user whose role cannot be broadcast then the presence will only be sent to the presence's
* user. On the other hand, the JID of the user that sent the presence won't be included if the
* room is semi-anon and the target occupant is not a moderator.
*
* @param presence the presence to broadcast.
*/
private void broadcastPresence(Presence presence) {
if (presence == null) {
return;
}
if (hasToCheckRoleToBroadcastPresence()) {
Element frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
// Check if we can broadcast the presence for this role
if (!canBroadcastPresence(frag.element("item").attributeValue("role"))) {
// Just send the presence to the sender of the presence
try {
MUCRole occupant = getOccupant(presence.getFrom().getResource());
occupant.send(presence);
}
catch (UserNotFoundException e) {
// Do nothing
}
return;
}
}
// Broadcast presence to occupants hosted by other cluster nodes
BroascastPresenceRequest request = new BroascastPresenceRequest(this, presence);
CacheFactory.doClusterTask(request);
// Broadcast presence to occupants connected to this JVM
request = new BroascastPresenceRequest(this, presence);
request.setOriginator(true);
request.run();
}
public void broadcast(BroascastPresenceRequest presenceRequest) {
String jid = null;
Presence presence = presenceRequest.getPresence();
Element frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
// Don't include the occupant's JID if the room is semi-anon and the new occupant
// is not a moderator
if (!canAnyoneDiscoverJID()) {
jid = frag.element("item").attributeValue("jid");
}
for (MUCRole occupant : occupants.values()) {
if (!occupant.isLocal()) {
continue;
}
// Don't include the occupant's JID if the room is semi-anon and the new occupant
// is not a moderator
if (!canAnyoneDiscoverJID()) {
if (MUCRole.Role.moderator == occupant.getRole()) {
frag.element("item").addAttribute("jid", jid);
}
else {
frag.element("item").addAttribute("jid", null);
}
}
occupant.send(presence);
}
}
private void broadcast(Message message) {
// Broadcast message to occupants hosted by other cluster nodes
BroascastMessageRequest request = new BroascastMessageRequest(this, message, occupants.size());
CacheFactory.doClusterTask(request);
// Broadcast message to occupants connected to this JVM
request = new BroascastMessageRequest(this, message, occupants.size());
request.setOriginator(true);
request.run();
}
public void broadcast(BroascastMessageRequest messageRequest) {
Message message = messageRequest.getMessage();
// Add message to the room history
roomHistory.addMessage(message);
// Send message to occupants connected to this JVM
for (MUCRole occupant : occupants.values()) {
// Do not send broadcast messages to deaf occupants or occupants hosted in
// other cluster nodes
if (occupant.isLocal() && !occupant.isVoiceOnly()) {
occupant.send(message);
}
}
if (messageRequest.isOriginator() && isLogEnabled()) {
MUCRole senderRole = null;
JID senderAddress;
if (message.getFrom() != null && message.getFrom().getResource() != null) {
senderRole = occupants.get(message.getFrom().getResource().toLowerCase());
}
if (senderRole == null) {
// The room itself is sending the message
senderAddress = getRole().getRoleAddress();
}
else {
// An occupant is sending the message
senderAddress = senderRole.getUserAddress();
}
// Log the conversation
server.logConversation(this, message, senderAddress);
}
server.messageBroadcastedTo(messageRequest.getOccupants());
}
/**
* An empty role that represents the room itself in the chatroom. Chatrooms need to be able to
* speak (server messages) and so must have their own role in the chatroom.
*/
private class RoomRole implements MUCRole {
private MUCRoom room;
private RoomRole(MUCRoom room) {
this.room = room;
}
public Presence getPresence() {
return null;
}
public void setPresence(Presence presence) {
}
public void setRole(MUCRole.Role newRole) {
}
public MUCRole.Role getRole() {
return MUCRole.Role.moderator;
}
public void setAffiliation(MUCRole.Affiliation newAffiliation) {
}
public MUCRole.Affiliation getAffiliation() {
return MUCRole.Affiliation.owner;
}
public void changeNickname(String nickname) {
}
public String getNickname() {
return null;
}
public boolean isVoiceOnly() {
return false;
}
public boolean isLocal() {
return true;
}
public NodeID getNodeID() {
return XMPPServer.getInstance().getNodeID();
}
public MUCRoom getChatRoom() {
return room;
}
private JID crJID = null;
public JID getRoleAddress() {
if (crJID == null) {
crJID = new JID(room.getName(), server.getServiceDomain(), null, true);
}
return crJID;
}
public JID getUserAddress() {
return null;
}
public void send(Packet packet) {
room.send(packet);
}
public void destroy() {
}
}
public long getChatLength() {
return endTime - startTime;
}
/**
* Updates all the presences of the given user with the new affiliation and role information. Do
* nothing if the given jid is not present in the room. If the user has joined the room from
* several client resources, all his/her occupants' presences will be updated.
*
* @param bareJID the bare jid of the user to update his/her role.
* @param newAffiliation the new affiliation for the JID.
* @param newRole the new role for the JID.
* @return the list of updated presences of all the client resources that the client used to
* join the room.
* @throws NotAllowedException If trying to change the moderator role to an owner or an admin or
* if trying to ban an owner or an administrator.
*/
private List<Presence> changeOccupantAffiliation(String bareJID, MUCRole.Affiliation newAffiliation, MUCRole.Role newRole)
throws NotAllowedException {
List<Presence> presences = new ArrayList<Presence>();
// Get all the roles (i.e. occupants) of this user based on his/her bare JID
List<MUCRole> roles = occupantsByBareJID.get(bareJID);
if (roles == null) {
return presences;
}
// Collect all the updated presences of these roles
for (MUCRole role : roles) {
// Update the presence with the new affiliation and role
role.setAffiliation(newAffiliation);
role.setRole(newRole);
// Notify the othe cluster nodes to update the occupant
CacheFactory.doClusterTask(new UpdateOccupant(this, role));
// Prepare a new presence to be sent to all the room occupants
presences.add(role.getPresence().createCopy());
}
// Answer all the updated presences
return presences;
}
/**
* Updates the presence of the given user with the new role information. Do nothing if the given
* jid is not present in the room.
*
* @param jid the full jid of the user to update his/her role.
* @param newRole the new role for the JID.
* @return the updated presence of the user or null if none.
* @throws NotAllowedException If trying to change the moderator role to an owner or an admin.
*/
private Presence changeOccupantRole(JID jid, MUCRole.Role newRole) throws NotAllowedException {
// Try looking the role in the bare JID list
MUCRole role = occupantsByFullJID.get(jid);
if (role != null) {
// Update the presence with the new role
role.setRole(newRole);
// Notify the othe cluster nodes to update the occupant
CacheFactory.doClusterTask(new UpdateOccupant(this, role));
// Prepare a new presence to be sent to all the room occupants
return role.getPresence().createCopy();
}
return null;
}
public void addFirstOwner(String bareJID) {
owners.add(bareJID);
}
public List<Presence> addOwner(String bareJID, MUCRole sendRole) throws ForbiddenException {
MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
if (MUCRole.Affiliation.owner != sendRole.getAffiliation()) {
throw new ForbiddenException();
}
// Check if user is already an owner
if (owners.contains(bareJID)) {
// Do nothing
return Collections.emptyList();
}
owners.add(bareJID);
// Remove the user from other affiliation lists
if (removeAdmin(bareJID)) {
oldAffiliation = MUCRole.Affiliation.admin;
}
else if (removeMember(bareJID)) {
oldAffiliation = MUCRole.Affiliation.member;
}
else if (removeOutcast(bareJID)) {
oldAffiliation = MUCRole.Affiliation.outcast;
}
// Update the DB if the room is persistent
MUCPersistenceManager.saveAffiliationToDB(
this,
bareJID,
null,
MUCRole.Affiliation.owner,
oldAffiliation);
// Update the presence with the new affiliation and inform all occupants
try {
return changeOccupantAffiliation(bareJID, MUCRole.Affiliation.owner,
MUCRole.Role.moderator);
}
catch (NotAllowedException e) {
// We should never receive this exception....in theory
return null;
}
}
private boolean removeOwner(String bareJID) {
return owners.remove(bareJID);
}
public List<Presence> addAdmin(String bareJID, MUCRole sendRole) throws ForbiddenException,
ConflictException {
MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
if (MUCRole.Affiliation.owner != sendRole.getAffiliation()) {
throw new ForbiddenException();
}
// Check that the room always has an owner
if (owners.contains(bareJID) && owners.size() == 1) {
throw new ConflictException();
}
// Check if user is already an admin
if (admins.contains(bareJID)) {
// Do nothing
return Collections.emptyList();
}
admins.add(bareJID);
// Remove the user from other affiliation lists
if (removeOwner(bareJID)) {
oldAffiliation = MUCRole.Affiliation.owner;
}
else if (removeMember(bareJID)) {
oldAffiliation = MUCRole.Affiliation.member;
}
else if (removeOutcast(bareJID)) {
oldAffiliation = MUCRole.Affiliation.outcast;
}
// Update the DB if the room is persistent
MUCPersistenceManager.saveAffiliationToDB(
this,
bareJID,
null,
MUCRole.Affiliation.admin,
oldAffiliation);
// Update the presence with the new affiliation and inform all occupants
try {
return changeOccupantAffiliation(bareJID, MUCRole.Affiliation.admin,
MUCRole.Role.moderator);
}
catch (NotAllowedException e) {
// We should never receive this exception....in theory
return null;
}
}
private boolean removeAdmin(String bareJID) {
return admins.remove(bareJID);
}
public List<Presence> addMember(String bareJID, String nickname, MUCRole sendRole)
throws ForbiddenException, ConflictException {
MUCRole.Affiliation oldAffiliation = (members.containsKey(bareJID) ?
MUCRole.Affiliation.member : MUCRole.Affiliation.none);
if (isMembersOnly()) {
if (!canOccupantsInvite()) {
if (MUCRole.Affiliation.admin != sendRole.getAffiliation()
&& MUCRole.Affiliation.owner != sendRole.getAffiliation()) {
throw new ForbiddenException();
}
}
}
else {
if (MUCRole.Affiliation.admin != sendRole.getAffiliation()
&& MUCRole.Affiliation.owner != sendRole.getAffiliation()) {
throw new ForbiddenException();
}
}
// Check if the desired nickname is already reserved for another member
if (nickname != null && nickname.trim().length() > 0 && members.containsValue(nickname)) {
if (!nickname.equals(members.get(bareJID))) {
throw new ConflictException();
}
}
// Check that the room always has an owner
if (owners.contains(bareJID) && owners.size() == 1) {
throw new ConflictException();
}
// Associate the reserved nickname with the bareJID. If nickname is null then associate an
// empty string
members.put(bareJID, (nickname == null ? "" : nickname));
// Remove the user from other affiliation lists
if (removeOwner(bareJID)) {
oldAffiliation = MUCRole.Affiliation.owner;
}
else if (removeAdmin(bareJID)) {
oldAffiliation = MUCRole.Affiliation.admin;
}
else if (removeOutcast(bareJID)) {
oldAffiliation = MUCRole.Affiliation.outcast;
}
// Update the DB if the room is persistent
MUCPersistenceManager.saveAffiliationToDB(
this,
bareJID,
nickname,
MUCRole.Affiliation.member,
oldAffiliation);
// Update the presence with the new affiliation and inform all occupants
try {
return changeOccupantAffiliation(bareJID, MUCRole.Affiliation.member,
MUCRole.Role.participant);
}
catch (NotAllowedException e) {
// We should never receive this exception....in theory
return null;
}
}
private boolean removeMember(String bareJID) {
boolean answer = members.containsKey(bareJID);
members.remove(bareJID);
return answer;
}
public List<Presence> addOutcast(String bareJID, String reason, MUCRole senderRole)
throws NotAllowedException, ForbiddenException, ConflictException {
MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
if (MUCRole.Affiliation.admin != senderRole.getAffiliation()
&& MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
throw new ForbiddenException();
}
// Check that the room always has an owner
if (owners.contains(bareJID) && owners.size() == 1) {
throw new ConflictException();
}
// Check if user is already an outcast
if (outcasts.contains(bareJID)) {
// Do nothing
return Collections.emptyList();
}
// Update the presence with the new affiliation and inform all occupants
// actorJID will be null if the room itself (ie. via admin console) made the request
JID actorJID = senderRole.getUserAddress();
List<Presence> updatedPresences = changeOccupantAffiliation(
bareJID,
MUCRole.Affiliation.outcast,
MUCRole.Role.none);
Element frag;
// Add the status code and reason why the user was banned to the presences that will
// be sent to the room occupants (the banned user will not receive this presences)
for (Presence presence : updatedPresences) {
frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
// Add the status code 301 that indicates that the user was banned
frag.addElement("status").addAttribute("code", "301");
// Add the reason why the user was banned
if (reason != null && reason.trim().length() > 0) {
frag.element("item").addElement("reason").setText(reason);
}
// Remove the banned users from the room. If a user has joined the room from
// different client resources, he/she will be kicked from all the client resources
// Effectively kick the occupant from the room
kickPresence(presence, actorJID);
}
// Update the affiliation lists
outcasts.add(bareJID);
// Remove the user from other affiliation lists
if (removeOwner(bareJID)) {
oldAffiliation = MUCRole.Affiliation.owner;
}
else if (removeAdmin(bareJID)) {
oldAffiliation = MUCRole.Affiliation.admin;
}
else if (removeMember(bareJID)) {
oldAffiliation = MUCRole.Affiliation.member;
}
// Update the DB if the room is persistent
MUCPersistenceManager.saveAffiliationToDB(
this,
bareJID,
null,
MUCRole.Affiliation.outcast,
oldAffiliation);
return updatedPresences;
}
private boolean removeOutcast(String bareJID) {
return outcasts.remove(bareJID);
}
public List<Presence> addNone(String bareJID, MUCRole senderRole) throws ForbiddenException,
ConflictException {
MUCRole.Affiliation oldAffiliation = MUCRole.Affiliation.none;
if (MUCRole.Affiliation.admin != senderRole.getAffiliation()
&& MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
throw new ForbiddenException();
}
// Check that the room always has an owner
if (owners.contains(bareJID) && owners.size() == 1) {
throw new ConflictException();
}
List<Presence> updatedPresences = null;
boolean wasMember = members.containsKey(bareJID) || admins.contains(bareJID) ||
owners.contains(bareJID);
// Remove the user from ALL the affiliation lists
if (removeOwner(bareJID)) {
oldAffiliation = MUCRole.Affiliation.owner;
}
else if (removeAdmin(bareJID)) {
oldAffiliation = MUCRole.Affiliation.admin;
}
else if (removeMember(bareJID)) {
oldAffiliation = MUCRole.Affiliation.member;
}
else if (removeOutcast(bareJID)) {
oldAffiliation = MUCRole.Affiliation.outcast;
}
// Remove the affiliation of this user from the DB if the room is persistent
MUCPersistenceManager.removeAffiliationFromDB(this, bareJID, oldAffiliation);
// Update the presence with the new affiliation and inform all occupants
try {
MUCRole.Role newRole;
if (isMembersOnly() && wasMember) {
newRole = MUCRole.Role.none;
}
else {
newRole = isModerated() ? MUCRole.Role.visitor : MUCRole.Role.participant;
}
updatedPresences = changeOccupantAffiliation(bareJID, MUCRole.Affiliation.none, newRole);
if (isMembersOnly() && wasMember) {
// If the room is members-only, remove the user from the room including a status
// code of 321 to indicate that the user was removed because of an affiliation
// change
Element frag;
// Add the status code to the presences that will be sent to the room occupants
for (Presence presence : updatedPresences) {
// Set the presence as an unavailable presence
presence.setType(Presence.Type.unavailable);
presence.setStatus(null);
frag = presence.getChildElement("x", "http://jabber.org/protocol/muc#user");
// Add the status code 321 that indicates that the user was removed because of
// an affiliation change
frag.addElement("status").addAttribute("code", "321");
// Remove the ex-member from the room. If a user has joined the room from
// different client resources, he/she will be kicked from all the client
// resources.
// Effectively kick the occupant from the room
JID actorJID = senderRole.getUserAddress();
kickPresence(presence, actorJID);
}
}
}
catch (NotAllowedException e) {
// We should never receive this exception....in theory
}
return updatedPresences;
}
public boolean isLocked() {
return lockedTime > 0;
}
public boolean isManuallyLocked() {
return lockedTime > 0 && creationDate.getTime() != lockedTime;
}
public void presenceUpdated(MUCRole occupantRole, Presence newPresence) {
// Ask other cluster nodes to update the presence of the occupant
UpdatePresence request = new UpdatePresence(this, newPresence.createCopy(), occupantRole.getNickname());
CacheFactory.doClusterTask(request);
// Update the presence of the occupant
request = new UpdatePresence(this, newPresence.createCopy(), occupantRole.getNickname());
request.setOriginator(true);
request.run();
// Broadcast new presence of occupant
broadcastPresence(occupantRole.getPresence().createCopy());
}
/**
* Updates the presence of an occupant with the new presence included in the request.
*
* @param updatePresence request to update an occupant's presence.
*/
public void presenceUpdated(UpdatePresence updatePresence) {
MUCRole occupantRole = occupants.get(updatePresence.getNickname().toLowerCase());
if (occupantRole != null) {
occupantRole.setPresence(updatePresence.getPresence());
}
else {
Log.debug("Failed to update presence of room occupant. Occupant nickname: " + updatePresence.getNickname());
}
}
public void occupantUpdated(UpdateOccupant update) {
RemoteMUCRole occupantRole = (RemoteMUCRole) occupants.get(update.getNickname().toLowerCase());
if (occupantRole != null) {
occupantRole.setPresence(update.getPresence());
occupantRole.setRole(update.getRole());
occupantRole.setAffiliation(update.getAffiliation());
}
else {
Log.debug("Failed to update information of room occupant. Occupant nickname: " + update.getNickname());
}
}
public void nicknameChanged(MUCRole occupantRole, Presence newPresence, String oldNick, String newNick) {
// Ask other cluster nodes to update the nickname of the occupant
ChangeNickname request = new ChangeNickname(this, oldNick, newNick, newPresence.createCopy());
CacheFactory.doClusterTask(request);
// Update the nickname of the occupant
request = new ChangeNickname(this, oldNick, newNick, newPresence.createCopy());
request.setOriginator(true);
request.run();
// Broadcast new presence of occupant
broadcastPresence(occupantRole.getPresence().createCopy());
}
public void nicknameChanged(ChangeNickname changeNickname) {
MUCRole occupantRole = occupants.get(changeNickname.getOldNick().toLowerCase());
if (occupantRole != null) {
// Update the role with the new info
occupantRole.setPresence(changeNickname.getPresence());
occupantRole.changeNickname(changeNickname.getNewNick());
// Fire event that user changed his nickname
server.fireNicknameChanged(getRole().getRoleAddress(), occupantRole.getUserAddress(),
changeNickname.getOldNick(), changeNickname.getNewNick());
// Associate the existing MUCRole with the new nickname
occupants.put(changeNickname.getNewNick().toLowerCase(), occupantRole);
// Remove the old nickname
occupants.remove(changeNickname.getOldNick().toLowerCase());
}
}
public void changeSubject(Message packet, MUCRole role) throws ForbiddenException {
if ((canOccupantsChangeSubject() && role.getRole().compareTo(MUCRole.Role.visitor) < 0) ||
MUCRole.Role.moderator == role.getRole()) {
// Do nothing if the new subject is the same as the existing one
if (packet.getSubject().equals(subject)) {
return;
}
// Set the new subject to the room
subject = packet.getSubject();
MUCPersistenceManager.updateRoomSubject(this);
// Notify all the occupants that the subject has changed
packet.setFrom(role.getRoleAddress());
send(packet);
}
else {
throw new ForbiddenException();
}
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public void sendInvitation(JID to, String reason, MUCRole senderRole, List<Element> extensions)
throws ForbiddenException {
if (!isMembersOnly() || canOccupantsInvite()
|| MUCRole.Affiliation.admin == senderRole.getAffiliation()
|| MUCRole.Affiliation.owner == senderRole.getAffiliation()) {
// If the room is not members-only OR if the room is members-only and anyone can send
// invitations or the sender is an admin or an owner, then send the invitation
Message message = new Message();
message.setFrom(role.getRoleAddress());
message.setTo(to);
// Add a list of extensions sent with the original message invitation (if any)
if (extensions != null) {
for(Element element : extensions) {
element.setParent(null);
message.getElement().add(element);
}
}
Element frag = message.addChildElement("x", "http://jabber.org/protocol/muc#user");
// ChatUser will be null if the room itself (ie. via admin console) made the request
if (senderRole.getUserAddress() != null) {
frag.addElement("invite").addAttribute("from", senderRole.getUserAddress().toBareJID());
}
if (reason != null && reason.length() > 0) {
Element invite = frag.element("invite");
if (invite == null) {
invite = frag.addElement("invite");
}
invite.addElement("reason").setText(reason);
}
if (isPasswordProtected()) {
frag.addElement("password").setText(getPassword());
}
// Include the jabber:x:conference information for backward compatibility
frag = message.addChildElement("x", "jabber:x:conference");
frag.addAttribute("jid", role.getRoleAddress().toBareJID());
// Send the message with the invitation
router.route(message);
}
else {
throw new ForbiddenException();
}
}
public void sendInvitationRejection(JID to, String reason, JID sender) {
Message message = new Message();
message.setFrom(role.getRoleAddress());
message.setTo(to);
Element frag = message.addChildElement("x", "http://jabber.org/protocol/muc#user");
frag.addElement("decline").addAttribute("from", sender.toBareJID());
if (reason != null && reason.length() > 0) {
frag.element("decline").addElement("reason").setText(reason);
}
// Send the message with the invitation
router.route(message);
}
public IQOwnerHandler getIQOwnerHandler() {
return iqOwnerHandler;
}
public IQAdminHandler getIQAdminHandler() {
return iqAdminHandler;
}
public MUCRoomHistory getRoomHistory() {
return roomHistory;
}
public Collection<String> getOwners() {
return Collections.unmodifiableList(owners);
}
public Collection<String> getAdmins() {
return Collections.unmodifiableList(admins);
}
public Collection<String> getMembers() {
return Collections.unmodifiableMap(members).keySet();
}
public Collection<String> getOutcasts() {
return Collections.unmodifiableList(outcasts);
}
public Collection<MUCRole> getModerators() {
List<MUCRole> moderators = new ArrayList<MUCRole>();
for (MUCRole role : occupants.values()) {
if (MUCRole.Role.moderator == role.getRole()) {
moderators.add(role);
}
}
return moderators;
}
public Collection<MUCRole> getParticipants() {
List<MUCRole> participants = new ArrayList<MUCRole>();
for (MUCRole role : occupants.values()) {
if (MUCRole.Role.participant == role.getRole()) {
participants.add(role);
}
}
return participants;
}
public Presence addModerator(JID jid, MUCRole senderRole) throws ForbiddenException {
if (MUCRole.Affiliation.admin != senderRole.getAffiliation()
&& MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
throw new ForbiddenException();
}
// Update the presence with the new role and inform all occupants
try {
return changeOccupantRole(jid, MUCRole.Role.moderator);
}
catch (NotAllowedException e) {
// We should never receive this exception....in theory
return null;
}
}
public Presence addParticipant(JID jid, String reason, MUCRole senderRole)
throws NotAllowedException, ForbiddenException {
if (MUCRole.Role.moderator != senderRole.getRole()) {
throw new ForbiddenException();
}
// Update the presence with the new role and inform all occupants
Presence updatedPresence = changeOccupantRole(jid, MUCRole.Role.participant);
if (updatedPresence != null) {
Element frag = updatedPresence.getChildElement(
"x", "http://jabber.org/protocol/muc#user");
// Add the reason why the user was granted voice
if (reason != null && reason.trim().length() > 0) {
frag.element("item").addElement("reason").setText(reason);
}
}
return updatedPresence;
}
public Presence addVisitor(JID jid, MUCRole senderRole) throws NotAllowedException,
ForbiddenException {
if (MUCRole.Role.moderator != senderRole.getRole()) {
throw new ForbiddenException();
}
return changeOccupantRole(jid, MUCRole.Role.visitor);
}
public Presence kickOccupant(JID jid, JID actorJID, String reason)
throws NotAllowedException {
// Update the presence with the new role and inform all occupants
Presence updatedPresence = changeOccupantRole(jid, MUCRole.Role.none);
if (updatedPresence != null) {
Element frag = updatedPresence.getChildElement(
"x", "http://jabber.org/protocol/muc#user");
// Add the status code 307 that indicates that the user was kicked
frag.addElement("status").addAttribute("code", "307");
// Add the reason why the user was kicked
if (reason != null && reason.trim().length() > 0) {
frag.element("item").addElement("reason").setText(reason);
}
// Effectively kick the occupant from the room
kickPresence(updatedPresence, actorJID);
}
return updatedPresence;
}
/**
* Kicks the occupant from the room. This means that the occupant will receive an unavailable
* presence with the actor that initiated the kick (if any). The occupant will also be removed
* from the occupants lists.
*
* @param kickPresence the presence of the occupant to kick from the room.
* @param actorJID The JID of the actor that initiated the kick or <tt>null</tt> if the info
* was not provided.
*/
private void kickPresence(Presence kickPresence, JID actorJID) {
MUCRole kickedRole;
// Get the role to kick
kickedRole = occupants.get(kickPresence.getFrom().getResource().toLowerCase());
if (kickedRole != null) {
kickPresence = kickPresence.createCopy();
// Add the actor's JID that kicked this user from the room
if (actorJID != null && actorJID.toString().length() > 0) {
Element frag = kickPresence.getChildElement(
"x", "http://jabber.org/protocol/muc#user");
frag.element("item").addElement("actor").addAttribute("jid", actorJID.toBareJID());
}
// Send the unavailable presence to the banned user
kickedRole.send(kickPresence);
// Remove the occupant from the room's occupants lists
OccupantLeftEvent event = new OccupantLeftEvent(this, kickedRole);
event.setOriginator(true);
event.run();
// Remove the occupant from the room's occupants lists
event = new OccupantLeftEvent(this, kickedRole);
CacheFactory.doClusterTask(event);
}
}
public boolean canAnyoneDiscoverJID() {
return canAnyoneDiscoverJID;
}
public void setCanAnyoneDiscoverJID(boolean canAnyoneDiscoverJID) {
this.canAnyoneDiscoverJID = canAnyoneDiscoverJID;
}
public boolean canOccupantsChangeSubject() {
return canOccupantsChangeSubject;
}
public void setCanOccupantsChangeSubject(boolean canOccupantsChangeSubject) {
this.canOccupantsChangeSubject = canOccupantsChangeSubject;
}
public boolean canOccupantsInvite() {
return canOccupantsInvite;
}
public void setCanOccupantsInvite(boolean canOccupantsInvite) {
this.canOccupantsInvite = canOccupantsInvite;
}
public String getNaturalLanguageName() {
return naturalLanguageName;
}
public void setNaturalLanguageName(String naturalLanguageName) {
this.naturalLanguageName = naturalLanguageName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isMembersOnly() {
return membersOnly;
}
public List<Presence> setMembersOnly(boolean membersOnly) {
List<Presence> presences = new ArrayList<Presence>();
if (membersOnly && !this.membersOnly) {
// If the room was not members-only and now it is, kick occupants that aren't member
// of the room
for (MUCRole occupant : occupants.values()) {
if (occupant.getAffiliation().compareTo(MUCRole.Affiliation.member) > 0) {
try {
presences.add(kickOccupant(occupant.getRoleAddress(), null,
LocaleUtils.getLocalizedString("muc.roomIsNowMembersOnly")));
}
catch (NotAllowedException e) {
Log.error(e);
}
}
}
}
this.membersOnly = membersOnly;
return presences;
}
public boolean isLogEnabled() {
return logEnabled;
}
public void setLogEnabled(boolean logEnabled) {
this.logEnabled = logEnabled;
}
public void setLoginRestrictedToNickname(boolean restricted) {
this.loginRestrictedToNickname = restricted;
}
public boolean isLoginRestrictedToNickname() {
return loginRestrictedToNickname;
}
public void setChangeNickname(boolean canChange) {
this.canChangeNickname = canChange;
}
public boolean canChangeNickname() {
return canChangeNickname;
}
public void setRegistrationEnabled(boolean registrationEnabled) {
this.registrationEnabled = registrationEnabled;
}
public boolean isRegistrationEnabled() {
return registrationEnabled;
}
public int getMaxUsers() {
return maxUsers;
}
public void setMaxUsers(int maxUsers) {
this.maxUsers = maxUsers;
}
public boolean isModerated() {
return moderated;
}
public void setModerated(boolean moderated) {
this.moderated = moderated;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isPasswordProtected() {
return password != null && password.trim().length() > 0;
}
public boolean isPersistent() {
return persistent;
}
public boolean wasSavedToDB() {
return isPersistent() && savedToDB;
}
public void setSavedToDB(boolean saved) {
this.savedToDB = saved;
}
public void setPersistent(boolean persistent) {
this.persistent = persistent;
}
public boolean isPublicRoom() {
return !isDestroyed && publicRoom;
}
public void setPublicRoom(boolean publicRoom) {
this.publicRoom = publicRoom;
}
public List<String> getRolesToBroadcastPresence() {
return Collections.unmodifiableList(rolesToBroadcastPresence);
}
public void setRolesToBroadcastPresence(List<String> rolesToBroadcastPresence) {
// TODO If the list changes while there are occupants in the room we must send available or
// unavailable presences of the affected occupants to the rest of the occupants
this.rolesToBroadcastPresence = rolesToBroadcastPresence;
}
/**
* Returns true if we need to check whether a presence could be sent or not.
*
* @return true if we need to check whether a presence could be sent or not.
*/
private boolean hasToCheckRoleToBroadcastPresence() {
// For performance reasons the check is done based on the size of the collection.
return rolesToBroadcastPresence.size() < 3;
}
public boolean canBroadcastPresence(String roleToBroadcast) {
return "none".equals(roleToBroadcast) || rolesToBroadcastPresence.contains(roleToBroadcast);
}
public void lock(MUCRole senderRole) throws ForbiddenException {
if (MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
throw new ForbiddenException();
}
if (isLocked()) {
// Do nothing if the room was already locked
return;
}
setLocked(true);
if (senderRole.getUserAddress() != null) {
// Send to the occupant that locked the room a message saying so
Message message = new Message();
message.setType(Message.Type.groupchat);
message.setBody(LocaleUtils.getLocalizedString("muc.locked"));
message.setFrom(getRole().getRoleAddress());
senderRole.send(message);
}
}
public void unlock(MUCRole senderRole) throws ForbiddenException {
if (MUCRole.Affiliation.owner != senderRole.getAffiliation()) {
throw new ForbiddenException();
}
if (!isLocked()) {
// Do nothing if the room was already unlocked
return;
}
setLocked(false);
if (senderRole.getUserAddress() != null) {
// Send to the occupant that unlocked the room a message saying so
Message message = new Message();
message.setType(Message.Type.groupchat);
message.setBody(LocaleUtils.getLocalizedString("muc.unlocked"));
message.setFrom(getRole().getRoleAddress());
senderRole.send(message);
}
}
private void setLocked(boolean locked) {
if (locked) {
this.lockedTime = System.currentTimeMillis();
}
else {
this.lockedTime = 0;
}
MUCPersistenceManager.updateRoomLock(this);
}
/**
* Sets the date when the room was locked. Initially when the room is created it is locked so
* the locked date is the creation date of the room. Afterwards, the room may be manually
* locked and unlocked so the locked date may be in these cases different than the creation
* date. A Date with time 0 means that the the room is unlocked.
*
* @param lockedTime the date when the room was locked.
*/
void setLockedDate(Date lockedTime) {
this.lockedTime = lockedTime.getTime();
}
/**
* Returns the date when the room was locked. Initially when the room is created it is locked so
* the locked date is the creation date of the room. Afterwards, the room may be manually
* locked and unlocked so the locked date may be in these cases different than the creation
* date. When the room is unlocked a Date with time 0 is returned.
*
* @return the date when the room was locked.
*/
Date getLockedDate() {
return new Date(lockedTime);
}
public List<Presence> addAdmins(List<String> newAdmins, MUCRole senderRole)
throws ForbiddenException, ConflictException {
List<Presence> answer = new ArrayList<Presence>(newAdmins.size());
for (String newAdmin : newAdmins) {
if (newAdmin.trim().length() > 0 && !admins.contains(newAdmin)) {
answer.addAll(addAdmin(newAdmin, senderRole));
}
}
return answer;
}
public List<Presence> addOwners(List<String> newOwners, MUCRole senderRole)
throws ForbiddenException {
List<Presence> answer = new ArrayList<Presence>(newOwners.size());
for (String newOwner : newOwners) {
if (newOwner.trim().length() > 0 && !owners.contains(newOwner)) {
answer.addAll(addOwner(newOwner, senderRole));
}
}
return answer;
}
public void saveToDB() {
// Make the room persistent
MUCPersistenceManager.saveToDB(this);
if (!savedToDB) {
// Set that the room is now in the DB
savedToDB = true;
// Save the existing room owners to the DB
for (String owner : owners) {
MUCPersistenceManager.saveAffiliationToDB(
this,
owner,
null,
MUCRole.Affiliation.owner,
MUCRole.Affiliation.none);
}
// Save the existing room admins to the DB
for (String admin : admins) {
MUCPersistenceManager.saveAffiliationToDB(
this,
admin,
null,
MUCRole.Affiliation.admin,
MUCRole.Affiliation.none);
}
// Save the existing room members to the DB
for (String bareJID : members.keySet()) {
MUCPersistenceManager.saveAffiliationToDB(this, bareJID, members.get(bareJID),
MUCRole.Affiliation.member, MUCRole.Affiliation.none);
}
// Save the existing room outcasts to the DB
for (String outcast : outcasts) {
MUCPersistenceManager.saveAffiliationToDB(
this,
outcast,
null,
MUCRole.Affiliation.outcast,
MUCRole.Affiliation.none);
}
}
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeSafeUTF(out, name);
ExternalizableUtil.getInstance().writeLong(out, startTime);
ExternalizableUtil.getInstance().writeLong(out, lockedTime);
ExternalizableUtil.getInstance().writeStringList(out, owners);
ExternalizableUtil.getInstance().writeStringList(out, admins);
ExternalizableUtil.getInstance().writeStringMap(out, members);
ExternalizableUtil.getInstance().writeStringList(out, outcasts);
ExternalizableUtil.getInstance().writeSafeUTF(out, naturalLanguageName);
ExternalizableUtil.getInstance().writeSafeUTF(out, description);
ExternalizableUtil.getInstance().writeBoolean(out, canOccupantsChangeSubject);
ExternalizableUtil.getInstance().writeInt(out, maxUsers);
ExternalizableUtil.getInstance().writeStringList(out, rolesToBroadcastPresence);
ExternalizableUtil.getInstance().writeBoolean(out, publicRoom);
ExternalizableUtil.getInstance().writeBoolean(out, persistent);
ExternalizableUtil.getInstance().writeBoolean(out, moderated);
ExternalizableUtil.getInstance().writeBoolean(out, membersOnly);
ExternalizableUtil.getInstance().writeBoolean(out, canOccupantsInvite);
ExternalizableUtil.getInstance().writeSafeUTF(out, password);
ExternalizableUtil.getInstance().writeBoolean(out, canAnyoneDiscoverJID);
ExternalizableUtil.getInstance().writeBoolean(out, logEnabled);
ExternalizableUtil.getInstance().writeBoolean(out, loginRestrictedToNickname);
ExternalizableUtil.getInstance().writeBoolean(out, canChangeNickname);
ExternalizableUtil.getInstance().writeBoolean(out, registrationEnabled);
ExternalizableUtil.getInstance().writeSafeUTF(out, subject);
ExternalizableUtil.getInstance().writeLong(out, roomID);
ExternalizableUtil.getInstance().writeLong(out, creationDate.getTime());
ExternalizableUtil.getInstance().writeLong(out, modificationDate.getTime());
ExternalizableUtil.getInstance().writeBoolean(out, emptyDate != null);
if (emptyDate != null) {
ExternalizableUtil.getInstance().writeLong(out, emptyDate.getTime());
}
ExternalizableUtil.getInstance().writeBoolean(out, savedToDB);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = ExternalizableUtil.getInstance().readSafeUTF(in);
startTime = ExternalizableUtil.getInstance().readLong(in);
lockedTime = ExternalizableUtil.getInstance().readLong(in);
owners.addAll(ExternalizableUtil.getInstance().readStringList(in));
admins.addAll(ExternalizableUtil.getInstance().readStringList(in));
members.putAll(ExternalizableUtil.getInstance().readStringMap(in));
outcasts.addAll(ExternalizableUtil.getInstance().readStringList(in));
naturalLanguageName = ExternalizableUtil.getInstance().readSafeUTF(in);
description = ExternalizableUtil.getInstance().readSafeUTF(in);
canOccupantsChangeSubject = ExternalizableUtil.getInstance().readBoolean(in);
maxUsers = ExternalizableUtil.getInstance().readInt(in);
rolesToBroadcastPresence.addAll(ExternalizableUtil.getInstance().readStringList(in));
publicRoom = ExternalizableUtil.getInstance().readBoolean(in);
persistent = ExternalizableUtil.getInstance().readBoolean(in);
moderated = ExternalizableUtil.getInstance().readBoolean(in);
membersOnly = ExternalizableUtil.getInstance().readBoolean(in);
canOccupantsInvite = ExternalizableUtil.getInstance().readBoolean(in);
password = ExternalizableUtil.getInstance().readSafeUTF(in);
canAnyoneDiscoverJID = ExternalizableUtil.getInstance().readBoolean(in);
logEnabled = ExternalizableUtil.getInstance().readBoolean(in);
loginRestrictedToNickname = ExternalizableUtil.getInstance().readBoolean(in);
canChangeNickname = ExternalizableUtil.getInstance().readBoolean(in);
registrationEnabled = ExternalizableUtil.getInstance().readBoolean(in);
subject = ExternalizableUtil.getInstance().readSafeUTF(in);
roomID = ExternalizableUtil.getInstance().readLong(in);
creationDate = new Date(ExternalizableUtil.getInstance().readLong(in));
modificationDate = new Date(ExternalizableUtil.getInstance().readLong(in));
if (ExternalizableUtil.getInstance().readBoolean(in)) {
emptyDate = new Date(ExternalizableUtil.getInstance().readLong(in));
}
savedToDB = ExternalizableUtil.getInstance().readBoolean(in);
server = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
roomHistory = new MUCRoomHistory(this, new HistoryStrategy(server.getHistoryStrategy()));
PacketRouter packetRouter = XMPPServer.getInstance().getPacketRouter();
this.iqOwnerHandler = new IQOwnerHandler(this, packetRouter);
this.iqAdminHandler = new IQAdminHandler(this, packetRouter);
server = (MultiUserChatServerImpl) XMPPServer.getInstance().getMultiUserChatServer();
router = packetRouter;
}
public void updateConfiguration(LocalMUCRoom otherRoom) {
startTime = otherRoom.startTime;
lockedTime = otherRoom.lockedTime;
owners = otherRoom.owners;
admins = otherRoom.admins;
members = otherRoom.members;
outcasts = otherRoom.outcasts;
naturalLanguageName = otherRoom.naturalLanguageName;
description = otherRoom.description;
canOccupantsChangeSubject = otherRoom.canOccupantsChangeSubject;
maxUsers = otherRoom.maxUsers;
rolesToBroadcastPresence = otherRoom.rolesToBroadcastPresence;
publicRoom = otherRoom.publicRoom;
persistent = otherRoom.persistent;
moderated = otherRoom.moderated;
membersOnly = otherRoom.membersOnly;
canOccupantsInvite = otherRoom.canOccupantsInvite;
password = otherRoom.password;
canAnyoneDiscoverJID = otherRoom.canAnyoneDiscoverJID;
logEnabled = otherRoom.logEnabled;
loginRestrictedToNickname = otherRoom.loginRestrictedToNickname;
canChangeNickname = otherRoom.canChangeNickname;
registrationEnabled = otherRoom.registrationEnabled;
subject = otherRoom.subject;
roomID = otherRoom.roomID;
creationDate = otherRoom.creationDate;
modificationDate = otherRoom.modificationDate;
emptyDate = otherRoom.emptyDate;
savedToDB = otherRoom.savedToDB;
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 3084 $
* $Date: 2005-11-15 23:51:41 -0300 (Tue, 15 Nov 2005) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.spi;
import org.dom4j.Element;
import org.jivesoftware.openfire.PacketException;
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.muc.*;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.NotFoundException;
import org.xmpp.packet.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Representation of users interacting with the chat service. A user
* may join serveral rooms hosted by the chat service. That means that
* we are going to have an instance of this class for the user and several
* MUCRoles for each joined room.<p>
*
* This room occupant is being hosted by this JVM. When the room occupant
* is hosted by another cluster node then an instance of {@link RemoteMUCRole}
* will be used instead.
*
* @author Gaston Dombiak
*/
public class LocalMUCUser implements MUCUser {
/** The chat server this user belongs to. */
private MultiUserChatServer server;
/** Real system XMPPAddress for the user. */
private JID realjid;
/** Table: key roomName.toLowerCase(); value LocalMUCRole. */
private Map<String, LocalMUCRole> roles = new ConcurrentHashMap<String, LocalMUCRole>();
/** Deliver packets to users. */
private PacketRouter router;
/**
* Time of last packet sent.
*/
private long lastPacketTime;
/**
* Create a new chat user.
*
* @param chatserver the server the user belongs to.
* @param packetRouter the router for sending packets from this user.
* @param jid the real address of the user
*/
LocalMUCUser(MultiUserChatServerImpl chatserver, PacketRouter packetRouter, JID jid) {
this.realjid = jid;
this.router = packetRouter;
this.server = chatserver;
}
/**
* Returns true if the user is currently present in one or more rooms.
*
* @return true if the user is currently present in one or more rooms.
*/
public boolean isJoined() {
return !roles.isEmpty();
}
/**
* Get all roles for this user.
*
* @return Iterator over all roles for this user
*/
public Collection<LocalMUCRole> getRoles() {
return Collections.unmodifiableCollection(roles.values());
}
/**
* Adds the role of the user in a particular room.
*
* @param roomName The name of the room.
* @param role The new role of the user.
*/
public void addRole(String roomName, LocalMUCRole role) {
roles.put(roomName, role);
}
/**
* Removes the role of the user in a particular room.<p>
*
* Note: PREREQUISITE: A lock on this object has already been obtained.
*
* @param roomName The name of the room we're being removed
*/
public void removeRole(String roomName) {
roles.remove(roomName);
}
/**
* Get time (in milliseconds from System currentTimeMillis()) since last packet.
*
* @return The time when the last packet was sent from this user
*/
public long getLastPacketTime() {
return lastPacketTime;
}
/**
* Generate a conflict packet to indicate that the nickname being requested/used is already in
* use by another user.
*
* @param packet the packet to be bounced.
* @param error the reason why the operation failed.
*/
private void sendErrorPacket(Packet packet, PacketError.Condition error) {
if (packet instanceof IQ) {
IQ reply = IQ.createResultIQ((IQ) packet);
reply.setChildElement(((IQ) packet).getChildElement().createCopy());
reply.setError(error);
router.route(reply);
}
else {
Packet reply = packet.createCopy();
reply.setError(error);
reply.setFrom(packet.getTo());
reply.setTo(packet.getFrom());
router.route(reply);
}
}
/**
* Obtain the address of the user. The address is used by services like the core
* server packet router to determine if a packet should be sent to the handler.
* Handlers that are working on behalf of the server should use the generic server
* hostname address (e.g. server.com).
*
* @return the address of the packet handler.
*/
public JID getAddress() {
return realjid;
}
public void process(Packet packet) throws UnauthorizedException, PacketException {
if (packet instanceof IQ) {
process((IQ)packet);
}
else if (packet instanceof Message) {
process((Message)packet);
}
else if (packet instanceof Presence) {
process((Presence)packet);
}
}
/**
* This method does all packet routing in the chat server. Packet routing is actually very
* simple:
*
* <ul>
* <li>Discover the room the user is talking to (server packets are dropped)</li>
* <li>If the room is not registered and this is a presence "available" packet, try to join the
* room</li>
* <li>If the room is registered, and presence "unavailable" leave the room</li>
* <li>Otherwise, rewrite the sender address and send to the room.</li>
* </ul>
*
* @param packet The packet to route.
*/
public void process(Message packet) {
// Ignore messages of type ERROR sent to a room
if (Message.Type.error == packet.getType()) {
return;
}
lastPacketTime = System.currentTimeMillis();
JID recipient = packet.getTo();
String group = recipient.getNode();
if (group == null) {
// Ignore packets to the groupchat server
// In the future, we'll need to support TYPE_IQ queries to the server for MUC
Log.info(LocaleUtils.getLocalizedString("muc.error.not-supported") + " "
+ packet.toString());
}
else {
MUCRole role = roles.get(group);
if (role == null) {
if (server.hasChatRoom(group)) {
boolean declinedInvitation = false;
Element userInfo = null;
if (Message.Type.normal == packet.getType()) {
// An user that is not an occupant could be declining an invitation
userInfo = packet.getChildElement(
"x", "http://jabber.org/protocol/muc#user");
if (userInfo != null
&& userInfo.element("decline") != null) {
// A user has declined an invitation to a room
// WARNING: Potential fraud if someone fakes the "from" of the
// message with the JID of a member and sends a "decline"
declinedInvitation = true;
}
}
if (declinedInvitation) {
Element info = userInfo.element("decline");
server.getChatRoom(group).sendInvitationRejection(
new JID(info.attributeValue("to")),
info.elementTextTrim("reason"),
packet.getFrom());
}
else {
// The sender is not an occupant of the room
sendErrorPacket(packet, PacketError.Condition.not_acceptable);
}
}
else {
// The sender is not an occupant of a NON-EXISTENT room!!!
sendErrorPacket(packet, PacketError.Condition.recipient_unavailable);
}
}
else {
// Check and reject conflicting packets with conflicting roles
// In other words, another user already has this nickname
if (!role.getUserAddress().equals(packet.getFrom())) {
sendErrorPacket(packet, PacketError.Condition.conflict);
}
else {
try {
if (packet.getSubject() != null && packet.getSubject().trim().length() > 0
&& Message.Type.groupchat == packet.getType()) {
// An occupant is trying to change the room's subject
role.getChatRoom().changeSubject(packet, role);
}
else {
// An occupant is trying to send a private, send public message,
// invite someone to the room or reject an invitation
Message.Type type = packet.getType();
String resource = packet.getTo().getResource();
if (resource == null || resource.trim().length() == 0) {
resource = null;
}
if (resource == null && Message.Type.groupchat == type) {
// An occupant is trying to send a public message
role.getChatRoom().sendPublicMessage(packet, role);
}
else if (resource != null
&& (Message.Type.chat == type || Message.Type.normal == type)) {
// An occupant is trying to send a private message
role.getChatRoom().sendPrivatePacket(packet, role);
}
else if (resource == null && Message.Type.normal == type) {
// An occupant could be sending an invitation or declining an
// invitation
Element userInfo = packet.getChildElement(
"x",
"http://jabber.org/protocol/muc#user");
// Real real real UGLY TRICK!!! Will and MUST be solved when
// persistence will be added. Replace locking with transactions!
LocalMUCRoom room = (LocalMUCRoom) role.getChatRoom();
if (userInfo != null && userInfo.element("invite") != null) {
// An occupant is sending invitations
// Try to keep the list of extensions sent together with the
// message invitation. These extensions will be sent to the
// invitees.
List<Element> extensions = new ArrayList<Element>(packet
.getElement().elements());
extensions.remove(userInfo);
// Send invitations to invitees
for (Iterator it=userInfo.elementIterator("invite");it.hasNext();) {
Element info = (Element) it.next();
// Add the user as a member of the room if the room is
// members only
if (room.isMembersOnly()) {
room.lock.writeLock().lock();
try {
room.addMember(info.attributeValue("to"), null, role);
}
finally {
room.lock.writeLock().unlock();
}
}
// Send the invitation to the invitee
room.sendInvitation(new JID(info.attributeValue("to")),
info.elementTextTrim("reason"), role, extensions);
}
}
else if (userInfo != null
&& userInfo.element("decline") != null) {
// An occupant has declined an invitation
Element info = userInfo.element("decline");
room.sendInvitationRejection(new JID(info.attributeValue("to")),
info.elementTextTrim("reason"), packet.getFrom());
}
else {
sendErrorPacket(packet, PacketError.Condition.bad_request);
}
}
else {
sendErrorPacket(packet, PacketError.Condition.bad_request);
}
}
}
catch (ForbiddenException e) {
sendErrorPacket(packet, PacketError.Condition.forbidden);
}
catch (NotFoundException e) {
sendErrorPacket(packet, PacketError.Condition.recipient_unavailable);
}
catch (ConflictException e) {
sendErrorPacket(packet, PacketError.Condition.conflict);
}
}
}
}
}
public void process(IQ packet) {
// Ignore IQs of type ERROR or RESULT sent to a room
if (IQ.Type.error == packet.getType()) {
return;
}
lastPacketTime = System.currentTimeMillis();
JID recipient = packet.getTo();
String group = recipient.getNode();
if (group == null) {
// Ignore packets to the groupchat server
// In the future, we'll need to support TYPE_IQ queries to the server for MUC
Log.info(LocaleUtils.getLocalizedString("muc.error.not-supported") + " "
+ packet.toString());
}
else {
MUCRole role = roles.get(group);
if (role == null) {
// TODO: send error message to user (can't send packets to group you haven't
// joined)
}
else if (IQ.Type.result == packet.getType()) {
// Only process IQ result packet if it's a private packet sent to another
// room occupant
if (packet.getTo().getResource() != null) {
try {
// User is sending an IQ result packet to another room occupant
role.getChatRoom().sendPrivatePacket(packet, role);
}
catch (NotFoundException e) {
// Do nothing. No error will be sent to the sender of the IQ result packet
}
}
}
else {
// Check and reject conflicting packets with conflicting roles
// In other words, another user already has this nickname
if (!role.getUserAddress().equals(packet.getFrom())) {
sendErrorPacket(packet, PacketError.Condition.conflict);
}
else {
try {
Element query = packet.getElement().element("query");
if (query != null &&
"http://jabber.org/protocol/muc#owner".equals(query.getNamespaceURI())) {
role.getChatRoom().getIQOwnerHandler().handleIQ(packet, role);
}
else if (query != null &&
"http://jabber.org/protocol/muc#admin".equals(query.getNamespaceURI())) {
role.getChatRoom().getIQAdminHandler().handleIQ(packet, role);
}
else {
if (packet.getTo().getResource() != null) {
// User is sending an IQ packet to another room occupant
role.getChatRoom().sendPrivatePacket(packet, role);
}
else {
sendErrorPacket(packet, PacketError.Condition.bad_request);
}
}
}
catch (ForbiddenException e) {
sendErrorPacket(packet, PacketError.Condition.forbidden);
}
catch (NotFoundException e) {
sendErrorPacket(packet, PacketError.Condition.recipient_unavailable);
}
catch (ConflictException e) {
sendErrorPacket(packet, PacketError.Condition.conflict);
}
catch (NotAllowedException e) {
sendErrorPacket(packet, PacketError.Condition.not_allowed);
}
catch (Exception e) {
sendErrorPacket(packet, PacketError.Condition.internal_server_error);
}
}
}
}
}
public void process(Presence packet) {
// Ignore presences of type ERROR sent to a room
if (Presence.Type.error == packet.getType()) {
return;
}
lastPacketTime = System.currentTimeMillis();
JID recipient = packet.getTo();
String group = recipient.getNode();
if (group != null) {
MUCRole role = roles.get(group);
if (role == null) {
// If we're not already in a room, we either are joining it or it's not
// properly addressed and we drop it silently
if (recipient.getResource() != null
&& recipient.getResource().trim().length() > 0) {
if (packet.isAvailable()) {
try {
// Get or create the room
MUCRoom room = server.getChatRoom(group, packet.getFrom());
// User must support MUC in order to create a room
Element mucInfo = packet.getChildElement("x",
"http://jabber.org/protocol/muc");
HistoryRequest historyRequest = null;
String password = null;
// Check for password & requested history if client supports MUC
if (mucInfo != null) {
password = mucInfo.elementTextTrim("password");
if (mucInfo.element("history") != null) {
historyRequest = new HistoryRequest(mucInfo);
}
}
// The user joins the room
role = room.joinRoom(recipient.getResource().trim(),
password,
historyRequest,
this,
packet.createCopy());
// If the client that created the room is non-MUC compliant then
// unlock the room thus creating an "instant" room
if (mucInfo == null && room.isLocked() && !room.isManuallyLocked()) {
room.unlock(role);
}
}
catch (UnauthorizedException e) {
sendErrorPacket(packet, PacketError.Condition.not_authorized);
}
catch (ServiceUnavailableException e) {
sendErrorPacket(packet, PacketError.Condition.service_unavailable);
}
catch (UserAlreadyExistsException e) {
sendErrorPacket(packet, PacketError.Condition.conflict);
}
catch (RoomLockedException e) {
sendErrorPacket(packet, PacketError.Condition.recipient_unavailable);
}
catch (ForbiddenException e) {
sendErrorPacket(packet, PacketError.Condition.forbidden);
}
catch (RegistrationRequiredException e) {
sendErrorPacket(packet, PacketError.Condition.registration_required);
}
catch (ConflictException e) {
sendErrorPacket(packet, PacketError.Condition.conflict);
}
catch (NotAcceptableException e) {
sendErrorPacket(packet, PacketError.Condition.not_acceptable);
}
catch (NotAllowedException e) {
sendErrorPacket(packet, PacketError.Condition.not_allowed);
}
}
else {
// TODO: send error message to user (can't send presence to group you
// haven't joined)
}
}
else {
if (packet.isAvailable()) {
// A resource is required in order to join a room
sendErrorPacket(packet, PacketError.Condition.bad_request);
}
// TODO: send error message to user (can't send packets to group you haven't
// joined)
}
}
else {
// Check and reject conflicting packets with conflicting roles
// In other words, another user already has this nickname
if (!role.getUserAddress().equals(packet.getFrom())) {
sendErrorPacket(packet, PacketError.Condition.conflict);
}
else {
if (Presence.Type.unavailable == packet.getType()) {
try {
// TODO Consider that different nodes can be creating and processing this presence at the same time (when remote node went down)
removeRole(group);
role.getChatRoom().leaveRoom(role);
}
catch (Exception e) {
Log.error(e);
}
}
else {
try {
String resource = (recipient.getResource() == null
|| recipient.getResource().trim().length() == 0 ? null
: recipient.getResource().trim());
if (resource == null
|| role.getNickname().equalsIgnoreCase(resource)) {
// Occupant has changed his availability status
role.getChatRoom().presenceUpdated(role, packet);
}
else {
// Occupant has changed his nickname. Send two presences
// to each room occupant
// Check if occupants are allowed to change their nicknames
if (!role.getChatRoom().canChangeNickname()) {
sendErrorPacket(packet, PacketError.Condition.not_acceptable);
}
// Answer a conflic error if the new nickname is taken
else if (role.getChatRoom().hasOccupant(resource)) {
sendErrorPacket(packet, PacketError.Condition.conflict);
}
else {
// Send "unavailable" presence for the old nickname
Presence presence = role.getPresence().createCopy();
// Switch the presence to OFFLINE
presence.setType(Presence.Type.unavailable);
presence.setStatus(null);
// Add the new nickname and status 303 as properties
Element frag = presence.getChildElement("x",
"http://jabber.org/protocol/muc#user");
frag.element("item").addAttribute("nick", resource);
frag.addElement("status").addAttribute("code", "303");
role.getChatRoom().send(presence);
// Send availability presence for the new nickname
String oldNick = role.getNickname();
role.getChatRoom().nicknameChanged(role, packet, oldNick, resource);
}
}
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
}
}
}
}
}
}
\ No newline at end of file
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.spi;
import org.dom4j.Element;
import org.dom4j.tree.DefaultElement;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.cluster.NodeID;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.openfire.muc.cluster.OccupantAddedEvent;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/**
* Representation of a room occupant of a local room that is being hosted by
* another cluster node. An instance of this class will exist for each room
* occupant that is hosted by another cluster node. Local rooms keep track of
* local and remote occupants in a transparent way.
*
* @author Gaston Dombiak
*/
public class RemoteMUCRole implements MUCRole, Externalizable {
private MultiUserChatServer server;
private Presence presence;
private Role role;
private Affiliation affiliation;
private String nickname;
private boolean voiceOnly;
private JID roleAddress;
private JID userAddress;
private MUCRoom room;
private NodeID nodeID;
/**
* Do not use this constructor. Only used for Externalizable.
*/
public RemoteMUCRole() {
}
public RemoteMUCRole(MultiUserChatServer server, OccupantAddedEvent event) {
this.server = server;
presence = event.getPresence();
role = event.getRole();
affiliation = event.getAffiliation();
nickname = event.getNickname();
voiceOnly = event.isVoiceOnly();
roleAddress = event.getRoleAddress();
userAddress = event.getUserAddress();
room = event.getRoom();
this.nodeID = event.getNodeID();
}
public Presence getPresence() {
return presence;
}
public void setPresence(Presence presence) {
this.presence = presence;
}
public void setRole(Role newRole) {
this.role = newRole;
}
public Role getRole() {
return role;
}
public void setAffiliation(Affiliation newAffiliation) {
this.affiliation = newAffiliation;
}
public Affiliation getAffiliation() {
return affiliation;
}
public void changeNickname(String nickname) {
this.nickname = nickname;
setRoleAddress(new JID(room.getName(), server.getServiceDomain(), nickname, true));
}
private void setRoleAddress(JID jid) {
roleAddress = jid;
// Set the new sender of the user presence in the room
presence.setFrom(jid);
}
public String getNickname() {
return nickname;
}
public void destroy() {
// Do nothing
}
public boolean isVoiceOnly() {
return voiceOnly;
}
public MUCRoom getChatRoom() {
return room;
}
public JID getRoleAddress() {
return roleAddress;
}
public JID getUserAddress() {
return userAddress;
}
public boolean isLocal() {
return false;
}
public NodeID getNodeID() {
return nodeID;
}
public void send(Packet packet) {
XMPPServer.getInstance().getPacketRouter().route(packet);
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeSerializable(out, (DefaultElement) presence.getElement());
ExternalizableUtil.getInstance().writeInt(out, role.ordinal());
ExternalizableUtil.getInstance().writeInt(out, affiliation.ordinal());
ExternalizableUtil.getInstance().writeSafeUTF(out, nickname);
ExternalizableUtil.getInstance().writeBoolean(out, voiceOnly);
ExternalizableUtil.getInstance().writeSerializable(out, roleAddress);
ExternalizableUtil.getInstance().writeSerializable(out, userAddress);
ExternalizableUtil.getInstance().writeByteArray(out, nodeID.toByteArray());
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
presence = new Presence((Element)ExternalizableUtil.getInstance().readSerializable(in), true);
role = Role.values()[ExternalizableUtil.getInstance().readInt(in)];
affiliation = Affiliation.values()[ExternalizableUtil.getInstance().readInt(in)];
nickname = ExternalizableUtil.getInstance().readSafeUTF(in);
voiceOnly = ExternalizableUtil.getInstance().readBoolean(in);
roleAddress = (JID) ExternalizableUtil.getInstance().readSerializable(in);
userAddress = (JID) ExternalizableUtil.getInstance().readSerializable(in);
nodeID = NodeID.getInstance(ExternalizableUtil.getInstance().readByteArray(in));
}
}
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.muc.spi;
import org.jivesoftware.openfire.PacketException;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.MUCUser;
import org.xmpp.packet.*;
/**
* User hosted by another cluster node that is presente in a local room. Remote users are
* only created when processing unavailable presences sent when the node hosting the actual
* user went down. Each cluster node remaining in the cluster will create an unavailable
* presence for each user hosted in the cluster node that went down as a way to indicate
* the remaining room occupants that the user is offline.
*
* @author Gaston Dombiak
*/
public class RemoteMUCUser implements MUCUser {
/**
* JID of the user hosted by other cluster node.
*/
private JID realjid;
/**
* Local room that keep a reference to the RemoteMUCRole for this user.
*/
private LocalMUCRoom room;
public RemoteMUCUser(JID realjid, LocalMUCRoom room) {
this.realjid = realjid;
this.room = room;
}
public JID getAddress() {
return realjid;
}
public void process(Packet packet) throws UnauthorizedException, PacketException {
if (packet instanceof IQ) {
throw new UnsupportedOperationException("Cannot process IQ packets of remote users: " + packet);
}
else if (packet instanceof Message) {
throw new UnsupportedOperationException("Cannot process Message packets of remote users: " + packet);
}
else if (packet instanceof Presence) {
process((Presence)packet);
}
}
private void process(Presence presence) {
if (presence.getType() == Presence.Type.unavailable) {
MUCRole mucRole = room.getOccupantByFullJID(realjid);
if (mucRole != null) {
room.leaveRoom(mucRole);
}
}
else {
throw new UnsupportedOperationException("Cannot process Presence packets of remote users: " + presence);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment