Commit 52a517c6 authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

More clustering work (MUC support).

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@8787 b35dd754-fafc-0310-a699-88a17e54d16e
parent a3ac7356
...@@ -99,7 +99,7 @@ public class XMPPServer { ...@@ -99,7 +99,7 @@ public class XMPPServer {
private Date startDate; private Date startDate;
private boolean initialized = false; private boolean initialized = false;
private NodeID nodeID; private NodeID nodeID;
private static final NodeID DEFAULT_NODE_ID = new NodeID(new byte[0]); private static final NodeID DEFAULT_NODE_ID = NodeID.getInstance(new byte[0]);
/** /**
* All modules loaded by this server * All modules loaded by this server
...@@ -313,7 +313,7 @@ public class XMPPServer { ...@@ -313,7 +313,7 @@ public class XMPPServer {
name = JiveGlobals.getProperty("xmpp.domain", "127.0.0.1").toLowerCase(); name = JiveGlobals.getProperty("xmpp.domain", "127.0.0.1").toLowerCase();
version = new Version(3, 4, 0, Version.ReleaseStatus.Alpha, 1); version = new Version(3, 4, 0, Version.ReleaseStatus.Alpha, 2);
if ("true".equals(JiveGlobals.getXMLProperty("setup"))) { if ("true".equals(JiveGlobals.getXMLProperty("setup"))) {
setupMode = false; setupMode = false;
} }
......
...@@ -334,6 +334,20 @@ public class ClusterManager { ...@@ -334,6 +334,20 @@ public class ClusterManager {
return CacheFactory.isSeniorClusterMember(); return CacheFactory.isSeniorClusterMember();
} }
/**
* Returns the id of the node that is the senior cluster member. When not in a cluster the returned
* node id will be the {@link XMPPServer#getNodeID()}.
*
* @return the id of the node that is the senior cluster member.
*/
public static NodeID getSeniorClusterMember() {
byte[] clusterMemberID = CacheFactory.getSeniorClusterMemberID();
if (clusterMemberID == null) {
return XMPPServer.getInstance().getNodeID();
}
return NodeID.getInstance(clusterMemberID);
}
private static class Event { private static class Event {
private EventType type; private EventType type;
private byte[] nodeID; private byte[] nodeID;
......
...@@ -17,7 +17,9 @@ import java.io.Externalizable; ...@@ -17,7 +17,9 @@ import java.io.Externalizable;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInput; import java.io.ObjectInput;
import java.io.ObjectOutput; import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
/** /**
* Class which wraps the byte[] we use to identify cluster members. The main reason * Class which wraps the byte[] we use to identify cluster members. The main reason
...@@ -29,12 +31,38 @@ import java.util.Arrays; ...@@ -29,12 +31,38 @@ import java.util.Arrays;
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class NodeID implements Externalizable { public class NodeID implements Externalizable {
private static List<NodeID> instances = new ArrayList<NodeID>();
private byte[] nodeID; private byte[] nodeID;
public static synchronized NodeID getInstance(byte[] nodeIdBytes) {
for (NodeID nodeID : instances) {
if (nodeID.equals(nodeIdBytes)) {
return nodeID;
}
}
NodeID answer = new NodeID(nodeIdBytes);
instances.add(answer);
return answer;
}
public static synchronized void deleteInstance(byte[] nodeIdBytes) {
NodeID toDelete = null;
for (NodeID nodeID : instances) {
if (nodeID.equals(nodeIdBytes)) {
toDelete = nodeID;
break;
}
}
if (toDelete != null) {
instances.remove(toDelete);
}
}
public NodeID() { public NodeID() {
} }
public NodeID(byte[] nodeIdBytes) { private NodeID(byte[] nodeIdBytes) {
this.nodeID = nodeIdBytes; this.nodeID = nodeIdBytes;
} }
......
...@@ -306,7 +306,7 @@ public class IQDiscoInfoHandler extends IQHandler implements ClusterEventListene ...@@ -306,7 +306,7 @@ public class IQDiscoInfoHandler extends IQHandler implements ClusterEventListene
public void leftCluster(byte[] nodeID) { public void leftCluster(byte[] nodeID) {
if (ClusterManager.isSeniorClusterMember()) { if (ClusterManager.isSeniorClusterMember()) {
NodeID leftNode = new NodeID(nodeID); NodeID leftNode = NodeID.getInstance(nodeID);
// Remove server features added by node that is gone // Remove server features added by node that is gone
for (Map.Entry<String, Set<NodeID>> entry : serverFeatures.entrySet()) { for (Map.Entry<String, Set<NodeID>> entry : serverFeatures.entrySet()) {
String namespace = entry.getKey(); String namespace = entry.getKey();
......
...@@ -365,7 +365,7 @@ public class IQDiscoItemsHandler extends IQHandler implements ServerFeaturesProv ...@@ -365,7 +365,7 @@ public class IQDiscoItemsHandler extends IQHandler implements ServerFeaturesProv
public void leftCluster(byte[] nodeID) { public void leftCluster(byte[] nodeID) {
if (ClusterManager.isSeniorClusterMember()) { if (ClusterManager.isSeniorClusterMember()) {
NodeID leftNode = new NodeID(nodeID); NodeID leftNode = NodeID.getInstance(nodeID);
for (Map.Entry<String, ClusteredServerItem> entry : serverItems.entrySet()) { for (Map.Entry<String, ClusteredServerItem> entry : serverItems.entrySet()) {
String jid = entry.getKey(); String jid = entry.getKey();
Lock lock = LockManager.getLock(jid + "item"); Lock lock = LockManager.getLock(jid + "item");
......
...@@ -11,21 +11,16 @@ ...@@ -11,21 +11,16 @@
package org.jivesoftware.openfire.muc; package org.jivesoftware.openfire.muc;
import org.dom4j.Element;
import org.jivesoftware.openfire.muc.spi.LocalMUCRole;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.Log;
import org.xmpp.packet.Message;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.*;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.TimeZone;
import org.jivesoftware.openfire.muc.spi.MUCRoleImpl;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.FastDateFormat;
import org.dom4j.Element;
import org.xmpp.packet.Message;
/** /**
* Represents the amount of history requested by an occupant while joining a room. There are * Represents the amount of history requested by an occupant while joining a room. There are
...@@ -136,7 +131,7 @@ public class HistoryRequest { ...@@ -136,7 +131,7 @@ public class HistoryRequest {
* @param joinRole the user that will receive the history. * @param joinRole the user that will receive the history.
* @param roomHistory the history of the room. * @param roomHistory the history of the room.
*/ */
public void sendHistory(MUCRoleImpl joinRole, MUCRoomHistory roomHistory) { public void sendHistory(LocalMUCRole joinRole, MUCRoomHistory roomHistory) {
if (!isConfigured()) { if (!isConfigured()) {
Iterator history = roomHistory.getMessageHistory(); Iterator history = roomHistory.getMessageHistory();
while (history.hasNext()) { while (history.hasNext()) {
......
...@@ -11,13 +11,13 @@ ...@@ -11,13 +11,13 @@
package org.jivesoftware.openfire.muc; package org.jivesoftware.openfire.muc;
import org.jivesoftware.openfire.muc.cluster.UpdateHistoryStrategy;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log; import org.jivesoftware.util.Log;
import org.jivesoftware.util.cache.CacheFactory;
import org.xmpp.packet.Message; import org.xmpp.packet.Message;
import java.util.Iterator; import java.util.*;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
/** /**
...@@ -96,10 +96,18 @@ public class HistoryStrategy { ...@@ -96,10 +96,18 @@ public class HistoryStrategy {
* @param max the maximum number of messages to store in applicable strategies. * @param max the maximum number of messages to store in applicable strategies.
*/ */
public void setMaxNumber(int max) { public void setMaxNumber(int max) {
if (maxNumber == max) {
// Do nothing since value has not changed
return;
}
this.maxNumber = max; this.maxNumber = max;
if (contextPrefix != null){ if (contextPrefix != null){
JiveGlobals.setProperty(contextPrefix + ".maxNumber", Integer.toString(maxNumber)); JiveGlobals.setProperty(contextPrefix + ".maxNumber", Integer.toString(maxNumber));
} }
if (parent == null) {
// Update the history strategy of the MUC service
CacheFactory.doClusterTask(new UpdateHistoryStrategy(this));
}
} }
/** /**
...@@ -108,12 +116,20 @@ public class HistoryStrategy { ...@@ -108,12 +116,20 @@ public class HistoryStrategy {
* @param newType The new type of chat history to use. * @param newType The new type of chat history to use.
*/ */
public void setType(Type newType){ public void setType(Type newType){
if (type == newType) {
// Do nothing since value has not changed
return;
}
if (newType != null){ if (newType != null){
type = newType; type = newType;
} }
if (contextPrefix != null){ if (contextPrefix != null){
JiveGlobals.setProperty(contextPrefix + ".type", type.toString()); JiveGlobals.setProperty(contextPrefix + ".type", type.toString());
} }
if (parent == null) {
// Update the history strategy of the MUC service
CacheFactory.doClusterTask(new UpdateHistoryStrategy(this));
}
} }
/** /**
...@@ -195,6 +211,8 @@ public class HistoryStrategy { ...@@ -195,6 +211,8 @@ public class HistoryStrategy {
*/ */
public Iterator<Message> getMessageHistory(){ public Iterator<Message> getMessageHistory(){
LinkedList<Message> list = new LinkedList<Message>(history); LinkedList<Message> list = new LinkedList<Message>(history);
// Sort messages. Messages may be out of order when running inside of a cluster
Collections.sort(list, new MessageComparator());
return list.iterator(); return list.iterator();
} }
...@@ -207,6 +225,8 @@ public class HistoryStrategy { ...@@ -207,6 +225,8 @@ public class HistoryStrategy {
*/ */
public ListIterator<Message> getReverseMessageHistory(){ public ListIterator<Message> getReverseMessageHistory(){
LinkedList<Message> list = new LinkedList<Message>(history); LinkedList<Message> list = new LinkedList<Message>(history);
// Sort messages. Messages may be out of order when running inside of a cluster
Collections.sort(list, new MessageComparator());
return list.listIterator(list.size()); return list.listIterator(list.size());
} }
...@@ -227,14 +247,14 @@ public class HistoryStrategy { ...@@ -227,14 +247,14 @@ public class HistoryStrategy {
*/ */
public void setTypeFromString(String typeName) { public void setTypeFromString(String typeName) {
try { try {
setType(Type.valueOf(typeName)); type = Type.valueOf(typeName);
} }
catch (Exception e) { catch (Exception e) {
if (parent != null) { if (parent != null) {
setType(Type.defaulType); type = Type.defaulType;
} }
else { else {
setType(Type.number); type = Type.number;
} }
} }
} }
...@@ -251,7 +271,7 @@ public class HistoryStrategy { ...@@ -251,7 +271,7 @@ public class HistoryStrategy {
String maxNumberString = JiveGlobals.getProperty(prefix + ".maxNumber"); String maxNumberString = JiveGlobals.getProperty(prefix + ".maxNumber");
if (maxNumberString != null && maxNumberString.trim().length() > 0){ if (maxNumberString != null && maxNumberString.trim().length() > 0){
try { try {
setMaxNumber(Integer.parseInt(maxNumberString)); this.maxNumber = Integer.parseInt(maxNumberString);
} }
catch (Exception e){ catch (Exception e){
Log.info("Jive property " + prefix + ".maxNumber not a valid number."); Log.info("Jive property " + prefix + ".maxNumber not a valid number.");
...@@ -269,4 +289,12 @@ public class HistoryStrategy { ...@@ -269,4 +289,12 @@ public class HistoryStrategy {
public boolean hasChangedSubject() { public boolean hasChangedSubject() {
return roomSubject != null; return roomSubject != null;
} }
private static class MessageComparator implements Comparator<Message> {
public int compare(Message o1, Message o2) {
String stamp1 = o1.getChildElement("x", "jabber:x:delay").attributeValue("stamp");
String stamp2 = o2.getChildElement("x", "jabber:x:delay").attributeValue("stamp");
return stamp1.compareTo(stamp2);
}
}
} }
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
package org.jivesoftware.openfire.muc; package org.jivesoftware.openfire.muc;
import org.dom4j.Element; import org.jivesoftware.openfire.cluster.NodeID;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.Packet; import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence; import org.xmpp.packet.Presence;
...@@ -27,6 +27,139 @@ import org.xmpp.packet.Presence; ...@@ -27,6 +27,139 @@ import org.xmpp.packet.Presence;
*/ */
public interface MUCRole { public interface MUCRole {
/**
* Obtain the current presence status of a user in a chatroom.
*
* @return The presence of the user in the room.
*/
public Presence getPresence();
/**
* Set the current presence status of a user in a chatroom.
*
* @param presence The presence of the user in the room.
*/
public void setPresence(Presence presence);
/**
* Call this method to promote or demote a user's role in a chatroom.
* It is common for the chatroom or other chat room members to change
* the role of users (a moderator promoting another user to moderator
* status for example).<p>
* <p/>
* Owning ChatUsers should have their membership roles updated.
*
* @param newRole The new role that the user will play.
* @throws NotAllowedException Thrown if trying to change the moderator role to an owner or
* administrator.
*/
public void setRole(Role newRole) throws NotAllowedException;
/**
* Obtain the role state of the user.
*
* @return The role status of this user.
*/
public Role getRole();
/**
* Call this method to promote or demote a user's affiliation in a chatroom.
*
* @param newAffiliation the new affiliation that the user will play.
* @throws NotAllowedException thrown if trying to ban an owner or an administrator.
*/
public void setAffiliation(Affiliation newAffiliation) throws NotAllowedException;
/**
* Obtain the affiliation state of the user.
*
* @return The affiliation status of this user.
*/
public Affiliation getAffiliation();
/**
* Changes the nickname of the occupant within the room to the new nickname.
*
* @param nickname the new nickname of the occupant in the room.
*/
void changeNickname(String nickname);
/**
* Obtain the nickname for the user in the chatroom.
*
* @return The user's nickname in the room or null if invisible.
*/
public String getNickname();
/**
* Destroys this role after the occupant left the room. This role will be
* removed from MUCUser.
*/
public void destroy();
/**
* Returns true if the room occupant does not want to get messages broadcasted to all
* room occupants. This type of users are called "deaf" occupants. Deaf occupants will still
* be able to get private messages, presences, IQ packets or room history.<p>
*
* To be a deaf occupant the initial presence sent to the room while joining the room has
* to include the following child element:
* <pre>
* &lt;x xmlns='http://jivesoftware.org/protocol/muc'&gt;
* &lt;deaf-occupant/&gt;
* &lt;/x&gt;
* </pre>
*
* Note that this is a custom extension to the MUC specification.
*
* @return true if the room occupant does not want to get messages broadcasted to all
* room occupants.
*/
boolean isVoiceOnly();
/**
* Obtain the chat room that hosts this user's role.
*
* @return The chatroom hosting this role.
*/
public MUCRoom getChatRoom();
/**
* Obtain the XMPPAddress representing this role in a room: room@server/nickname
*
* @return The Jabber ID that represents this role in the room.
*/
public JID getRoleAddress();
/**
* Obtain the XMPPAddress of the user that joined the room. A <tt>null</tt> null value
* represents the room's role.
*
* @return The address of the user that joined the room or null if this role belongs to the room itself.
*/
public JID getUserAddress();
/**
* Returns true if this room occupant is hosted by this JVM.
*
* @return true if this room occupant is hosted by this JVM
*/
public boolean isLocal();
/**
* Returns the id of the node that is hosting the room occupant.
*
* @return the id of the node that is hosting the room occupant.
*/
public NodeID getNodeID();
/**
* Sends a packet to the user.
*
* @param packet The packet to send
*/
public void send(Packet packet);
public enum Role { public enum Role {
/** /**
...@@ -141,125 +274,4 @@ public interface MUCRole { ...@@ -141,125 +274,4 @@ public interface MUCRole {
} }
} }
} }
/**
* Obtain the current presence status of a user in a chatroom.
*
* @return The presence of the user in the room.
*/
public Presence getPresence();
/**
* Returns the extended presence information that includes information about roles,
* affiliations, JIDs, etc.
*
* @return the extended presence information that includes information about roles,
* affiliations.
*/
public Element getExtendedPresenceInformation();
/**
* Set the current presence status of a user in a chatroom.
*
* @param presence The presence of the user in the room.
*/
public void setPresence(Presence presence);
/**
* Call this method to promote or demote a user's role in a chatroom.
* It is common for the chatroom or other chat room members to change
* the role of users (a moderator promoting another user to moderator
* status for example).<p>
* <p/>
* Owning ChatUsers should have their membership roles updated.
*
* @param newRole The new role that the user will play.
* @throws NotAllowedException Thrown if trying to change the moderator role to an owner or
* administrator.
*/
public void setRole(Role newRole) throws NotAllowedException;
/**
* Obtain the role state of the user.
*
* @return The role status of this user.
*/
public Role getRole();
/**
* Call this method to promote or demote a user's affiliation in a chatroom.
*
* @param newAffiliation the new affiliation that the user will play.
* @throws NotAllowedException thrown if trying to ban an owner or an administrator.
*/
public void setAffiliation(Affiliation newAffiliation) throws NotAllowedException;
/**
* Obtain the affiliation state of the user.
*
* @return The affiliation status of this user.
*/
public Affiliation getAffiliation();
/**
* Obtain the nickname for the user in the chatroom.
*
* @return The user's nickname in the room or null if invisible.
*/
public String getNickname();
/**
* Changes the nickname of the occupant within the room to the new nickname.
*
* @param nickname the new nickname of the occupant in the room.
*/
public void changeNickname(String nickname);
/**
* Returns true if the room occupant does not want to get messages broadcasted to all
* room occupants. This type of users are called "deaf" occupants. Deaf occupants will still
* be able to get private messages, presences, IQ packets or room history.<p>
*
* To be a deaf occupant the initial presence sent to the room while joining the room has
* to include the following child element:
* <pre>
* &lt;x xmlns='http://jivesoftware.org/protocol/muc'&gt;
* &lt;deaf-occupant/&gt;
* &lt;/x&gt;
* </pre>
*
* Note that this is a custom extension to the MUC specification.
*
* @return true if the room occupant does not want to get messages broadcasted to all
* room occupants.
*/
boolean isVoiceOnly();
/**
* Obtain the chat user that plays this role.
*
* @return The chatuser playing this role.
*/
public MUCUser getChatUser();
/**
* Obtain the chat room that hosts this user's role.
*
* @return The chatroom hosting this role.
*/
public MUCRoom getChatRoom();
/**
* Obtain the XMPPAddress representing this role in a room: room@server/nickname
*
* @return The Jabber ID that represents this role in the room.
*/
public JID getRoleAddress();
/**
* Sends a packet to the user.
*
* @param packet The packet to send
*/
public void send(Packet packet);
} }
\ No newline at end of file
...@@ -11,23 +11,26 @@ ...@@ -11,23 +11,26 @@
package org.jivesoftware.openfire.muc; package org.jivesoftware.openfire.muc;
import java.util.List;
import java.util.Date;
import java.util.Collection;
import org.dom4j.Element; import org.dom4j.Element;
import org.jivesoftware.database.JiveID;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.muc.spi.IQAdminHandler; import org.jivesoftware.openfire.muc.spi.IQAdminHandler;
import org.jivesoftware.openfire.muc.spi.IQOwnerHandler; import org.jivesoftware.openfire.muc.spi.IQOwnerHandler;
import org.jivesoftware.util.NotFoundException; import org.jivesoftware.openfire.muc.spi.LocalMUCRole;
import org.jivesoftware.util.JiveConstants; import org.jivesoftware.openfire.muc.spi.LocalMUCUser;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.user.UserAlreadyExistsException; import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.database.JiveID; import org.jivesoftware.util.JiveConstants;
import org.xmpp.packet.Presence; import org.jivesoftware.util.NotFoundException;
import org.xmpp.packet.Message;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet; import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence;
import java.io.Externalizable;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/** /**
...@@ -37,7 +40,7 @@ import org.xmpp.packet.Packet; ...@@ -37,7 +40,7 @@ import org.xmpp.packet.Packet;
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
@JiveID(JiveConstants.MUC_ROOM) @JiveID(JiveConstants.MUC_ROOM)
public interface MUCRoom { public interface MUCRoom extends Externalizable {
/** /**
* Get the name of this room. * Get the name of this room.
...@@ -139,13 +142,13 @@ public interface MUCRoom { ...@@ -139,13 +142,13 @@ public interface MUCRoom {
List<MUCRole> getOccupantsByBareJID(String jid) throws UserNotFoundException; List<MUCRole> getOccupantsByBareJID(String jid) throws UserNotFoundException;
/** /**
* Obtain the role of a given user in the room by his full JID. * Returns the role of a given user in the room by his full JID or <tt>null</tt>
* if no role was found for the specified user.
* *
* @param jid The full jid of the user you'd like to obtain * @param jid The full jid of the user you'd like to obtain
* @return The user's role in the room * @return The user's role in the room or null if not found.
* @throws UserNotFoundException If there is no user with the given nickname
*/ */
MUCRole getOccupantByFullJID(JID jid) throws UserNotFoundException; MUCRole getOccupantByFullJID(JID jid);
/** /**
* Obtain the roles of all users in the chatroom. * Obtain the roles of all users in the chatroom.
...@@ -209,7 +212,7 @@ public interface MUCRoom { ...@@ -209,7 +212,7 @@ public interface MUCRoom {
* @throws NotAcceptableException If the registered user is trying to join with a * @throws NotAcceptableException If the registered user is trying to join with a
* nickname different than the reserved nickname. * nickname different than the reserved nickname.
*/ */
MUCRole joinRoom(String nickname, String password, HistoryRequest historyRequest, MUCUser user, LocalMUCRole joinRoom(String nickname, String password, HistoryRequest historyRequest, LocalMUCUser user,
Presence presence) throws UnauthorizedException, UserAlreadyExistsException, Presence presence) throws UnauthorizedException, UserAlreadyExistsException,
RoomLockedException, ForbiddenException, RegistrationRequiredException, RoomLockedException, ForbiddenException, RegistrationRequiredException,
ConflictException, ServiceUnavailableException, NotAcceptableException; ConflictException, ServiceUnavailableException, NotAcceptableException;
...@@ -217,10 +220,9 @@ public interface MUCRoom { ...@@ -217,10 +220,9 @@ public interface MUCRoom {
/** /**
* Remove a member from the chat room. * Remove a member from the chat room.
* *
* @param nickname The user to remove * @param leaveRole room occupant that left the room.
* @throws UserNotFoundException If the nickname is not found.
*/ */
void leaveRoom(String nickname) throws UserNotFoundException; void leaveRoom(MUCRole leaveRole);
/** /**
* Destroys the room. Each occupant will be removed and will receive a presence stanza of type * Destroys the room. Each occupant will be removed and will receive a presence stanza of type
...@@ -417,13 +419,23 @@ public interface MUCRoom { ...@@ -417,13 +419,23 @@ public interface MUCRoom {
*/ */
public boolean isManuallyLocked(); public boolean isManuallyLocked();
/**
* An event callback fired whenever an occupant updated his presence in the chatroom.
*
* @param occupantRole occupant that changed his presence in the room.
* @param newPresence presence sent by the occupant.
*/
public void presenceUpdated(MUCRole occupantRole, Presence newPresence);
/** /**
* An event callback fired whenever an occupant changes his nickname within the chatroom. * An event callback fired whenever an occupant changes his nickname within the chatroom.
* *
* @param occupantRole occupant that changed his nickname in the room.
* @param newPresence presence sent by the occupant with the new nickname.
* @param oldNick old nickname within the room. * @param oldNick old nickname within the room.
* @param newNick new nickname within the room. * @param newNick new nickname within the room.
*/ */
public void nicknameChanged(String oldNick, String newNick); public void nicknameChanged(MUCRole occupantRole, Presence newPresence, String oldNick, String newNick);
/** /**
* Changes the room's subject if the occupant has enough permissions. The occupant must be * Changes the room's subject if the occupant has enough permissions. The occupant must be
......
...@@ -83,7 +83,7 @@ public final class MUCRoomHistory { ...@@ -83,7 +83,7 @@ public final class MUCRoomHistory {
// Set the Full JID as the "from" attribute // Set the Full JID as the "from" attribute
try { try {
MUCRole role = room.getOccupant(message.getFrom().getResource()); MUCRole role = room.getOccupant(message.getFrom().getResource());
delayElement.addAttribute("from", role.getChatUser().getAddress().toString()); delayElement.addAttribute("from", role.getUserAddress().toString());
} }
catch (UserNotFoundException e) { catch (UserNotFoundException e) {
// Ignore. // Ignore.
...@@ -105,8 +105,7 @@ public final class MUCRoomHistory { ...@@ -105,8 +105,7 @@ public final class MUCRoomHistory {
// Set the Full JID as the "from" attribute // Set the Full JID as the "from" attribute
try { try {
MUCRole role = room.getOccupant(packet.getFrom().getResource()); MUCRole role = room.getOccupant(packet.getFrom().getResource());
delayInformation.addAttribute("from", role.getChatUser().getAddress() delayInformation.addAttribute("from", role.getUserAddress().toString());
.toString());
} }
catch (UserNotFoundException e) { catch (UserNotFoundException e) {
// Ignore. // Ignore.
......
...@@ -11,12 +11,9 @@ ...@@ -11,12 +11,9 @@
package org.jivesoftware.openfire.muc; package org.jivesoftware.openfire.muc;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.openfire.ChannelHandler; import org.jivesoftware.openfire.ChannelHandler;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import java.util.Iterator;
/** /**
* The chat user is a separate user abstraction for interacting with * The chat user is a separate user abstraction for interacting with
* the chat server. Centralizing chat users to the Jabber entity that * the chat server. Centralizing chat users to the Jabber entity that
...@@ -32,13 +29,6 @@ import java.util.Iterator; ...@@ -32,13 +29,6 @@ import java.util.Iterator;
*/ */
public interface MUCUser extends ChannelHandler { public interface MUCUser extends ChannelHandler {
/**
* Obtain a user ID (useful for database indexing).
*
* @return The user's id number if any (-1 indicates the implementation doesn't support ids)
*/
long getID();
/** /**
* Obtain the address of the user. The address is used by services like the core * 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. * server packet router to determine if a packet should be sent to the handler.
...@@ -48,51 +38,4 @@ public interface MUCUser extends ChannelHandler { ...@@ -48,51 +38,4 @@ public interface MUCUser extends ChannelHandler {
* @return the address of the packet handler. * @return the address of the packet handler.
*/ */
public JID getAddress(); public JID getAddress();
/**
* Obtain the role of the user in a particular room.
*
* @param roomName The name of the room we're interested in
* @return The role the user plays in that room
* @throws NotFoundException if the user does not have a role in the given room
*/
MUCRole getRole(String roomName) throws NotFoundException;
/**
* Get all roles for this user.
*
* @return Iterator over all roles for this user
*/
Iterator<MUCRole> getRoles();
/**
* 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.
*/
void addRole(String roomName, MUCRole 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
*/
void removeRole(String roomName);
/**
* 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.
*/
boolean isJoined();
/**
* Get time (in milliseconds from System currentTimeMillis()) since last packet.
*
* @return The time when the last packet was sent from this user
*/
long getLastPacketTime();
} }
\ No newline at end of file
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
package org.jivesoftware.openfire.muc; package org.jivesoftware.openfire.muc;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.xmpp.component.Component; import org.xmpp.component.Component;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.Message; import org.xmpp.packet.Message;
...@@ -235,30 +234,6 @@ public interface MultiUserChatServer extends Component { ...@@ -235,30 +234,6 @@ public interface MultiUserChatServer extends Component {
*/ */
void removeChatRoom(String roomName); void removeChatRoom(String roomName);
/**
* Removes a user from all chat rooms.
*
* @param jabberID The user's normal jid, not the chat nickname jid.
*/
void removeUser(JID jabberID);
/**
* Obtain a chat user by XMPPAddress.
*
* @param userjid The XMPPAddress of the user.
* @return The chatuser corresponding to that XMPPAddress.
* @throws UserNotFoundException If the user is not found and can't be auto-created.
*/
MUCUser getChatUser(JID userjid) throws UserNotFoundException;
/**
* Broadcast a given message to all members of this chat room. The sender is always set to be
* the chatroom.
*
* @param msg The message to broadcast.
*/
void serverBroadcast(String msg);
/** /**
* Returns the total chat time of all rooms combined. * Returns the total chat time of all rooms combined.
* *
......
...@@ -36,11 +36,11 @@ import java.util.List; ...@@ -36,11 +36,11 @@ import java.util.List;
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class IQAdminHandler { public class IQAdminHandler {
private MUCRoomImpl room; private LocalMUCRoom room;
private PacketRouter router; private PacketRouter router;
public IQAdminHandler(MUCRoomImpl chatroom, PacketRouter packetRouter) { public IQAdminHandler(LocalMUCRoom chatroom, PacketRouter packetRouter) {
this.room = chatroom; this.room = chatroom;
this.router = packetRouter; this.router = packetRouter;
} }
...@@ -170,7 +170,7 @@ public class IQAdminHandler { ...@@ -170,7 +170,7 @@ public class IQAdminHandler {
for (MUCRole role : room.getModerators()) { for (MUCRole role : room.getModerators()) {
metaData = result.addElement("item", "http://jabber.org/protocol/muc#admin"); metaData = result.addElement("item", "http://jabber.org/protocol/muc#admin");
metaData.addAttribute("role", "moderator"); metaData.addAttribute("role", "moderator");
metaData.addAttribute("jid", role.getChatUser().getAddress().toString()); metaData.addAttribute("jid", role.getUserAddress().toString());
metaData.addAttribute("nick", role.getNickname()); metaData.addAttribute("nick", role.getNickname());
metaData.addAttribute("affiliation", role.getAffiliation().toString()); metaData.addAttribute("affiliation", role.getAffiliation().toString());
} }
...@@ -183,7 +183,7 @@ public class IQAdminHandler { ...@@ -183,7 +183,7 @@ public class IQAdminHandler {
for (MUCRole role : room.getParticipants()) { for (MUCRole role : room.getParticipants()) {
metaData = result.addElement("item", "http://jabber.org/protocol/muc#admin"); metaData = result.addElement("item", "http://jabber.org/protocol/muc#admin");
metaData.addAttribute("role", "participant"); metaData.addAttribute("role", "participant");
metaData.addAttribute("jid", role.getChatUser().getAddress().toString()); metaData.addAttribute("jid", role.getUserAddress().toString());
metaData.addAttribute("nick", role.getNickname()); metaData.addAttribute("nick", role.getNickname());
metaData.addAttribute("affiliation", role.getAffiliation().toString()); metaData.addAttribute("affiliation", role.getAffiliation().toString());
} }
...@@ -219,7 +219,7 @@ public class IQAdminHandler { ...@@ -219,7 +219,7 @@ public class IQAdminHandler {
else { else {
// Get the JID based on the requested nick // Get the JID based on the requested nick
nick = item.attributeValue("nick"); nick = item.attributeValue("nick");
jid = room.getOccupant(nick).getChatUser().getAddress(); jid = room.getOccupant(nick).getUserAddress();
} }
room.lock.writeLock().lock(); room.lock.writeLock().lock();
...@@ -262,8 +262,7 @@ public class IQAdminHandler { ...@@ -262,8 +262,7 @@ public class IQAdminHandler {
if (MUCRole.Role.moderator != senderRole.getRole()) { if (MUCRole.Role.moderator != senderRole.getRole()) {
throw new ForbiddenException(); throw new ForbiddenException();
} }
presences.add(room.kickOccupant(jid, presences.add(room.kickOccupant(jid, senderRole.getUserAddress(),
senderRole.getChatUser().getAddress(),
item.elementTextTrim("reason"))); item.elementTextTrim("reason")));
} }
} }
......
...@@ -11,6 +11,10 @@ ...@@ -11,6 +11,10 @@
package org.jivesoftware.openfire.muc.spi; 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.forms.DataForm; import org.jivesoftware.openfire.forms.DataForm;
import org.jivesoftware.openfire.forms.FormField; import org.jivesoftware.openfire.forms.FormField;
import org.jivesoftware.openfire.forms.spi.XDataFormImpl; import org.jivesoftware.openfire.forms.spi.XDataFormImpl;
...@@ -18,18 +22,16 @@ import org.jivesoftware.openfire.forms.spi.XFormFieldImpl; ...@@ -18,18 +22,16 @@ import org.jivesoftware.openfire.forms.spi.XFormFieldImpl;
import org.jivesoftware.openfire.muc.ConflictException; import org.jivesoftware.openfire.muc.ConflictException;
import org.jivesoftware.openfire.muc.ForbiddenException; import org.jivesoftware.openfire.muc.ForbiddenException;
import org.jivesoftware.openfire.muc.MUCRole; import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.openfire.muc.cluster.RoomUpdatedEvent;
import org.jivesoftware.openfire.*;
import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserNotFoundException;
import java.util.*; import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.cache.CacheFactory;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.QName;
import org.xmpp.packet.Presence;
import org.xmpp.packet.JID;
import org.xmpp.packet.IQ; import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError; import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence;
import java.util.*;
/** /**
* A handler for the IQ packet with namespace http://jabber.org/protocol/muc#owner. This kind of * A handler for the IQ packet with namespace http://jabber.org/protocol/muc#owner. This kind of
...@@ -39,7 +41,7 @@ import org.xmpp.packet.PacketError; ...@@ -39,7 +41,7 @@ import org.xmpp.packet.PacketError;
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class IQOwnerHandler { public class IQOwnerHandler {
private MUCRoomImpl room; private LocalMUCRoom room;
private PacketRouter router; private PacketRouter router;
...@@ -47,7 +49,7 @@ public class IQOwnerHandler { ...@@ -47,7 +49,7 @@ public class IQOwnerHandler {
private Element probeResult; private Element probeResult;
public IQOwnerHandler(MUCRoomImpl chatroom, PacketRouter packetRouter) { public IQOwnerHandler(LocalMUCRoom chatroom, PacketRouter packetRouter) {
this.room = chatroom; this.room = chatroom;
this.router = packetRouter; this.router = packetRouter;
init(); init();
...@@ -209,7 +211,7 @@ public class IQOwnerHandler { ...@@ -209,7 +211,7 @@ public class IQOwnerHandler {
else { else {
// Get the bare JID based on the requested nick // Get the bare JID based on the requested nick
nick = item.attributeValue("nick"); nick = item.attributeValue("nick");
bareJID = room.getOccupant(nick).getChatUser().getAddress().toBareJID(); bareJID = room.getOccupant(nick).getUserAddress().toBareJID();
} }
jids.put(bareJID, affiliation); jids.put(bareJID, affiliation);
} }
...@@ -313,6 +315,10 @@ public class IQOwnerHandler { ...@@ -313,6 +315,10 @@ public class IQOwnerHandler {
if (room.isLocked() && !room.isManuallyLocked()) { if (room.isLocked() && !room.isManuallyLocked()) {
room.unlock(senderRole); room.unlock(senderRole);
} }
if (!room.isDestroyed) {
// Let other cluster nodes that the room has been updated
CacheFactory.doClusterTask(new RoomUpdatedEvent(room));
}
} }
} }
......
...@@ -12,12 +12,12 @@ ...@@ -12,12 +12,12 @@
package org.jivesoftware.openfire.muc.spi; package org.jivesoftware.openfire.muc.spi;
import org.jivesoftware.database.DbConnectionManager; import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.openfire.PacketRouter; import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.muc.MUCRole; import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.MUCRoom; import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.muc.MultiUserChatServer; import org.jivesoftware.openfire.muc.MultiUserChatServer;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
...@@ -144,7 +144,7 @@ public class MUCPersistenceManager { ...@@ -144,7 +144,7 @@ public class MUCPersistenceManager {
* *
* @param room the room to load from the database if persistent * @param room the room to load from the database if persistent
*/ */
public static void loadFromDB(MUCRoomImpl room) { public static void loadFromDB(LocalMUCRoom room) {
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
try { try {
...@@ -294,7 +294,7 @@ public class MUCPersistenceManager { ...@@ -294,7 +294,7 @@ public class MUCPersistenceManager {
* *
* @param room The room to save its configuration. * @param room The room to save its configuration.
*/ */
public static void saveToDB(MUCRoomImpl room) { public static void saveToDB(LocalMUCRoom room) {
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
try { try {
...@@ -415,19 +415,19 @@ public class MUCPersistenceManager { ...@@ -415,19 +415,19 @@ public class MUCPersistenceManager {
* @param packetRouter the PacketRouter that loaded rooms will use to send packets. * @param packetRouter the PacketRouter that loaded rooms will use to send packets.
* @return a collection with all the persistent rooms. * @return a collection with all the persistent rooms.
*/ */
public static Collection<MUCRoom> loadRoomsFromDB(MultiUserChatServer chatserver, public static Collection<LocalMUCRoom> loadRoomsFromDB(MultiUserChatServer chatserver,
Date emptyDate, PacketRouter packetRouter) { Date emptyDate, PacketRouter packetRouter) {
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; PreparedStatement pstmt = null;
Map<Long,MUCRoom> rooms = new HashMap<Long,MUCRoom>(); Map<Long, LocalMUCRoom> rooms = new HashMap<Long, LocalMUCRoom>();
try { try {
con = DbConnectionManager.getConnection(); con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_ALL_ROOMS); pstmt = con.prepareStatement(LOAD_ALL_ROOMS);
pstmt.setString(1, StringUtils.dateToMillis(emptyDate)); pstmt.setString(1, StringUtils.dateToMillis(emptyDate));
ResultSet rs = pstmt.executeQuery(); ResultSet rs = pstmt.executeQuery();
MUCRoomImpl room = null; LocalMUCRoom room = null;
while (rs.next()) { while (rs.next()) {
room = new MUCRoomImpl(chatserver, rs.getString(4), packetRouter); room = new LocalMUCRoom(chatserver, rs.getString(4), packetRouter);
room.setID(rs.getLong(1)); room.setID(rs.getLong(1));
room.setCreationDate(new Date(Long.parseLong(rs.getString(2).trim()))); // creation date room.setCreationDate(new Date(Long.parseLong(rs.getString(2).trim()))); // creation date
room.setModificationDate(new Date(Long.parseLong(rs.getString(3).trim()))); // modification date room.setModificationDate(new Date(Long.parseLong(rs.getString(3).trim()))); // modification date
...@@ -478,7 +478,7 @@ public class MUCPersistenceManager { ...@@ -478,7 +478,7 @@ public class MUCPersistenceManager {
// Load the rooms conversations from the last two days // Load the rooms conversations from the last two days
rs = pstmt.executeQuery(); rs = pstmt.executeQuery();
while (rs.next()) { while (rs.next()) {
room = (MUCRoomImpl) rooms.get(rs.getLong(1)); room = (LocalMUCRoom) rooms.get(rs.getLong(1));
// Skip to the next position if the room does not exist // Skip to the next position if the room does not exist
if (room == null) { if (room == null) {
continue; continue;
...@@ -521,7 +521,7 @@ public class MUCPersistenceManager { ...@@ -521,7 +521,7 @@ public class MUCPersistenceManager {
long roomID = rs.getLong(1); long roomID = rs.getLong(1);
String jid = rs.getString(2); String jid = rs.getString(2);
MUCRole.Affiliation affiliation = MUCRole.Affiliation.valueOf(rs.getInt(3)); MUCRole.Affiliation affiliation = MUCRole.Affiliation.valueOf(rs.getInt(3));
room = (MUCRoomImpl) rooms.get(roomID); room = (LocalMUCRoom) rooms.get(roomID);
// Skip to the next position if the room does not exist // Skip to the next position if the room does not exist
if (room == null) { if (room == null) {
continue; continue;
...@@ -552,7 +552,7 @@ public class MUCPersistenceManager { ...@@ -552,7 +552,7 @@ public class MUCPersistenceManager {
pstmt = con.prepareStatement(LOAD_ALL_MEMBERS); pstmt = con.prepareStatement(LOAD_ALL_MEMBERS);
rs = pstmt.executeQuery(); rs = pstmt.executeQuery();
while (rs.next()) { while (rs.next()) {
room = (MUCRoomImpl) rooms.get(rs.getLong(1)); room = (LocalMUCRoom) rooms.get(rs.getLong(1));
// Skip to the next position if the room does not exist // Skip to the next position if the room does not exist
if (room == null) { if (room == null) {
continue; continue;
...@@ -626,7 +626,7 @@ public class MUCPersistenceManager { ...@@ -626,7 +626,7 @@ public class MUCPersistenceManager {
* *
* @param room the room to update its lock status in the database. * @param room the room to update its lock status in the database.
*/ */
public static void updateRoomLock(MUCRoomImpl room) { public static void updateRoomLock(LocalMUCRoom room) {
if (!room.isPersistent() || !room.wasSavedToDB()) { if (!room.isPersistent() || !room.wasSavedToDB()) {
return; return;
} }
......
...@@ -27,10 +27,11 @@ import org.jivesoftware.openfire.forms.FormField; ...@@ -27,10 +27,11 @@ import org.jivesoftware.openfire.forms.FormField;
import org.jivesoftware.openfire.forms.spi.XDataFormImpl; import org.jivesoftware.openfire.forms.spi.XDataFormImpl;
import org.jivesoftware.openfire.forms.spi.XFormFieldImpl; import org.jivesoftware.openfire.forms.spi.XFormFieldImpl;
import org.jivesoftware.openfire.muc.*; import org.jivesoftware.openfire.muc.*;
import org.jivesoftware.openfire.muc.cluster.*;
import org.jivesoftware.openfire.stats.Statistic; import org.jivesoftware.openfire.stats.Statistic;
import org.jivesoftware.openfire.stats.StatisticsManager; import org.jivesoftware.openfire.stats.StatisticsManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.*; import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.CacheFactory;
import org.xmpp.component.ComponentManager; import org.xmpp.component.ComponentManager;
import org.xmpp.packet.*; import org.xmpp.packet.*;
...@@ -107,12 +108,16 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -107,12 +108,16 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
/** /**
* chatrooms managed by this manager, table: key room name (String); value ChatRoom * chatrooms managed by this manager, table: key room name (String); value ChatRoom
*/ */
private Map<String,MUCRoom> rooms = new ConcurrentHashMap<String,MUCRoom>(); private Map<String, LocalMUCRoom> rooms = new ConcurrentHashMap<String, LocalMUCRoom>();
/** /**
* chat users managed by this manager, table: key user jid (XMPPAddress); value ChatUser * Chat users managed by this manager. This includes only users connected to this JVM.
* That means that when running inside of a cluster each node will have its own manager
* that in turn will keep its own list of locally connected.
*
* table: key user jid (XMPPAddress); value ChatUser
*/ */
private Map<JID, MUCUser> users = new ConcurrentHashMap<JID, MUCUser>(); private Map<JID, LocalMUCUser> users = new ConcurrentHashMap<JID, LocalMUCUser>();
private HistoryStrategy historyStrategy; private HistoryStrategy historyStrategy;
private RoutingTable routingTable = null; private RoutingTable routingTable = null;
...@@ -233,8 +238,9 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -233,8 +238,9 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
} }
// The packet is a normal packet that should possibly be sent to the room // The packet is a normal packet that should possibly be sent to the room
MUCUser user = getChatUser(packet.getFrom()); JID receipient = packet.getTo();
user.process(packet); String roomName = receipient != null ? receipient.getNode() : null;
getChatUser(packet.getFrom(), roomName).process(packet);
} }
catch (Exception e) { catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e); Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
...@@ -318,7 +324,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -318,7 +324,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
return; return;
} }
final long deadline = System.currentTimeMillis() - user_idle; final long deadline = System.currentTimeMillis() - user_idle;
for (MUCUser user : users.values()) { for (LocalMUCUser user : users.values()) {
try { try {
if (user.getLastPacketTime() < deadline) { if (user.getLastPacketTime() < deadline) {
// If user is not present in any room then remove the user from // If user is not present in any room then remove the user from
...@@ -328,12 +334,9 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -328,12 +334,9 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
continue; continue;
} }
// Kick the user from all the rooms that he/she had previuosly joined // Kick the user from all the rooms that he/she had previuosly joined
Iterator<MUCRole> roles = user.getRoles();
MUCRole role;
MUCRoom room; MUCRoom room;
Presence kickedPresence; Presence kickedPresence;
while (roles.hasNext()) { for (LocalMUCRole role : user.getRoles()) {
role = roles.next();
room = role.getChatRoom(); room = role.getChatRoom();
try { try {
kickedPresence = kickedPresence =
...@@ -401,6 +404,10 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -401,6 +404,10 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
*/ */
private class CleanupTask extends TimerTask { private class CleanupTask extends TimerTask {
public void run() { public void run() {
if (ClusterManager.isClusteringStarted() && !ClusterManager.isSeniorClusterMember()) {
// Do nothing if we are in a cluster and this JVM is not the senior cluster member
return;
}
try { try {
cleanupRooms(); cleanupRooms();
} }
...@@ -419,17 +426,20 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -419,17 +426,20 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
public MUCRoom getChatRoom(String roomName, JID userjid) throws NotAllowedException { public MUCRoom getChatRoom(String roomName, JID userjid) throws NotAllowedException {
MUCRoom room; LocalMUCRoom room;
boolean loaded = false;
boolean created = false;
synchronized (roomName.intern()) { synchronized (roomName.intern()) {
room = rooms.get(roomName); room = rooms.get(roomName);
if (room == null) { if (room == null) {
room = new MUCRoomImpl(this, roomName, router); room = new LocalMUCRoom(this, roomName, router);
// If the room is persistent load the configuration values from the DB // If the room is persistent load the configuration values from the DB
try { try {
// Try to load the room's configuration from the database (if the room is // Try to load the room's configuration from the database (if the room is
// persistent but was added to the DB after the server was started up or the // persistent but was added to the DB after the server was started up or the
// room may be an old room that was not present in memory) // room may be an old room that was not present in memory)
MUCPersistenceManager.loadFromDB((MUCRoomImpl) room); MUCPersistenceManager.loadFromDB((LocalMUCRoom) room);
loaded = true;
} }
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
// The room does not exist so check for creation permissions // The room does not exist so check for creation permissions
...@@ -444,31 +454,45 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -444,31 +454,45 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
} }
room.addFirstOwner(userjid.toBareJID()); room.addFirstOwner(userjid.toBareJID());
// Fire event that a new room has been created created = true;
for (MUCEventListener listener : listeners) {
listener.roomCreated(room.getRole().getRoleAddress());
}
} }
rooms.put(roomName, room); rooms.put(roomName, room);
} }
} }
if (created) {
// Fire event that a new room has been created
for (MUCEventListener listener : listeners) {
listener.roomCreated(room.getRole().getRoleAddress());
}
}
if (loaded || created) {
// Notify other cluster nodes that a new room is available
CacheFactory.doClusterTask(new RoomAvailableEvent(room));
for (MUCRole role : room.getOccupants()) {
if (role instanceof LocalMUCRole) {
CacheFactory.doClusterTask(new OccupantAddedEvent(room, role));
}
}
}
return room; return room;
} }
public MUCRoom getChatRoom(String roomName) { public MUCRoom getChatRoom(String roomName) {
MUCRoom room = rooms.get(roomName); boolean loaded = false;
LocalMUCRoom room = rooms.get(roomName);
if (room == null) { if (room == null) {
// Check if the room exists in the database and was not present in memory // Check if the room exists in the database and was not present in memory
synchronized (roomName.intern()) { synchronized (roomName.intern()) {
room = rooms.get(roomName); room = rooms.get(roomName);
if (room == null) { if (room == null) {
room = new MUCRoomImpl(this, roomName, router); room = new LocalMUCRoom(this, roomName, router);
// If the room is persistent load the configuration values from the DB // If the room is persistent load the configuration values from the DB
try { try {
// Try to load the room's configuration from the database (if the room is // Try to load the room's configuration from the database (if the room is
// persistent but was added to the DB after the server was started up or the // persistent but was added to the DB after the server was started up or the
// room may be an old room that was not present in memory) // room may be an old room that was not present in memory)
MUCPersistenceManager.loadFromDB((MUCRoomImpl) room); MUCPersistenceManager.loadFromDB((LocalMUCRoom) room);
loaded = true;
rooms.put(roomName, room); rooms.put(roomName, room);
} }
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
...@@ -478,6 +502,10 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -478,6 +502,10 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
} }
} }
if (loaded) {
// Notify other cluster nodes that a new room is available
CacheFactory.doClusterTask(new RoomAvailableEvent(room));
}
return room; return room;
} }
...@@ -490,9 +518,37 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -490,9 +518,37 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
public void removeChatRoom(String roomName) { public void removeChatRoom(String roomName) {
final MUCRoom room = rooms.remove(roomName); removeChatRoom(roomName, true);
}
/**
* Notification message indicating that the specified chat room was
* removed from some other cluster member.
*
* @param roomName the name of the room removed from the cluster.
*/
public void chatRoomRemoved(String roomName) {
removeChatRoom(roomName, false);
}
/**
* Notification message indicating that a chat room has been created
* in another cluster member.
*
* @param room the created room in another cluster node.
*/
public void chatRoomAdded(LocalMUCRoom room) {
rooms.put(room.getName(), room);
}
private void removeChatRoom(String roomName, boolean notify) {
MUCRoom room = rooms.remove(roomName);
if (room != null) { if (room != null) {
totalChatTime += room.getChatLength(); totalChatTime += room.getChatLength();
if (notify) {
// Notify other cluster nodes that a room has been removed
CacheFactory.doClusterTask(new RoomRemovedEvent(roomName));
}
} }
} }
...@@ -504,14 +560,17 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -504,14 +560,17 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
return historyStrategy; return historyStrategy;
} }
public void removeUser(JID jabberID) { /**
MUCUser user = users.remove(jabberID); * Removes a user from all chat rooms.
*
* @param jabberID The user's normal jid, not the chat nickname jid.
*/
private void removeUser(JID jabberID) {
LocalMUCUser user = users.remove(jabberID);
if (user != null) { if (user != null) {
Iterator<MUCRole> roles = user.getRoles(); for (LocalMUCRole role : user.getRoles()) {
while (roles.hasNext()) {
MUCRole role = roles.next();
try { try {
role.getChatRoom().leaveRoom(role.getNickname()); role.getChatRoom().leaveRoom(role);
} }
catch (Exception e) { catch (Exception e) {
Log.error(e); Log.error(e);
...@@ -520,27 +579,38 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -520,27 +579,38 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
} }
public MUCUser getChatUser(JID userjid) throws UserNotFoundException { /**
* Obtain a chat user by XMPPAddress. Only returns users that are connected to this JVM.
*
* @param userjid The XMPPAddress of the user.
* @param roomName name of the room to receive the packet.
* @return The chatuser corresponding to that XMPPAddress.
*/
private MUCUser getChatUser(JID userjid, String roomName) {
if (router == null) { if (router == null) {
throw new IllegalStateException("Not initialized"); throw new IllegalStateException("Not initialized");
} }
MUCUser user; LocalMUCUser user;
synchronized (userjid.toString().intern()) { synchronized (userjid.toString().intern()) {
user = users.get(userjid); user = users.get(userjid);
if (user == null) { if (user == null) {
user = new MUCUserImpl(this, router, userjid); if (roomName != null) {
// Check if the JID belong to a user hosted in another cluster node
LocalMUCRoom localMUCRoom = rooms.get(roomName);
if (localMUCRoom != null) {
MUCRole occupant = localMUCRoom.getOccupantByFullJID(userjid);
if (occupant != null && !occupant.isLocal()) {
return new RemoteMUCUser(userjid, localMUCRoom);
}
}
}
user = new LocalMUCUser(this, router, userjid);
users.put(userjid, user); users.put(userjid, user);
} }
} }
return user; return user;
} }
public void serverBroadcast(String msg) {
for (MUCRoom room : rooms.values()) {
room.serverBroadcast(msg);
}
}
public void setServiceName(String name) { public void setServiceName(String name) {
JiveGlobals.setProperty("xmpp.muc.service", name); JiveGlobals.setProperty("xmpp.muc.service", name);
} }
...@@ -811,8 +881,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -811,8 +881,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
params.add(getServiceDomain()); params.add(getServiceDomain());
Log.info(LocaleUtils.getLocalizedString("startup.starting.muc", params)); Log.info(LocaleUtils.getLocalizedString("startup.starting.muc", params));
// Load all the persistent rooms to memory // Load all the persistent rooms to memory
for (MUCRoom room : MUCPersistenceManager.loadRoomsFromDB(this, this.getCleanupDate(), for (LocalMUCRoom room : MUCPersistenceManager.loadRoomsFromDB(this, this.getCleanupDate(), router)) {
router)) {
rooms.put(room.getName().toLowerCase(), room); rooms.put(room.getName().toLowerCase(), room);
} }
// Add statistics // Add statistics
...@@ -883,15 +952,27 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -883,15 +952,27 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
/** /**
* Retuns the total number of occupants in all rooms in the server. * Retuns the total number of occupants in all rooms in the server.
* *
* @param onlyLocal true if only users connected to this JVM will be considered. Otherwise count cluster wise.
* @return the number of existing rooms in the server. * @return the number of existing rooms in the server.
*/ */
public int getNumberConnectedUsers() { public int getNumberConnectedUsers(boolean onlyLocal) {
int total = 0; int total = 0;
for (MUCUser user : users.values()) { for (LocalMUCUser user : users.values()) {
if (user.isJoined()) { if (user.isJoined()) {
total = total + 1; total = total + 1;
} }
} }
// Add users from remote cluster nodes
if (!onlyLocal) {
Collection<Object> results =
CacheFactory.doSynchronousClusterTask(new GetNumberConnectedUsers(), false);
for (Object result : results) {
if (result == null) {
continue;
}
total = total + (Integer) result;
}
}
return total; return total;
} }
...@@ -924,34 +1005,71 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -924,34 +1005,71 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
public void joinedCluster() { public void joinedCluster() {
// Disable the service until we know that we are the senior cluster member if (isServiceEnabled()) {
enableService(false, false); if (!ClusterManager.isSeniorClusterMember()) {
//TODO Do not disable the service. All nodes will host the service (unless it was disabled before) // Get transient rooms and persistent rooms with occupants from senior
//TODO Merge rooms existing in the cluster with the ones of the new node // cluster member and merge with local ones. If room configuration was
//TODO For rooms that exist in cluster and in new node then send presences of remote occupants to LOCAL occupants // changed in both places then latest configuration will be kept
List<RoomInfo> result = (List<RoomInfo>) CacheFactory.doSynchronousClusterTask(
new SeniorMemberRoomsRequest(), ClusterManager.getSeniorClusterMember().toByteArray());
if (result != null) {
for (RoomInfo roomInfo : result) {
LocalMUCRoom remoteRoom = roomInfo.getRoom();
LocalMUCRoom localRoom = rooms.get(remoteRoom.getName());
if (localRoom == null) {
// Create local room with remote information
localRoom = remoteRoom;
rooms.put(remoteRoom.getName(), localRoom);
}
else {
// Update local room with remote information
localRoom.updateConfiguration(remoteRoom);
}
// Add remote occupants to local room
// TODO Handle conflict of nicknames
for (OccupantAddedEvent event : roomInfo.getOccupants()) {
event.setSendPresence(true);
event.run();
}
}
}
}
}
} }
public void joinedCluster(byte[] nodeID) { public void joinedCluster(byte[] nodeID) {
//TODO Merge rooms existing in the cluster with the ones of the new node if (isServiceEnabled()) {
//TODO For rooms that exist in cluster and in new node then send presences of new remote occupants to LOCAL occupants List<RoomInfo> result =
(List<RoomInfo>) CacheFactory.doSynchronousClusterTask(new GetNewMemberRoomsRequest(), nodeID);
if (result != null) {
for (RoomInfo roomInfo : result) {
LocalMUCRoom remoteRoom = roomInfo.getRoom();
LocalMUCRoom localRoom = rooms.get(remoteRoom.getName());
if (localRoom == null) {
// Create local room with remote information
localRoom = remoteRoom;
rooms.put(remoteRoom.getName(), localRoom);
}
// Add remote occupants to local room
for (OccupantAddedEvent event : roomInfo.getOccupants()) {
event.setSendPresence(true);
event.run();
}
}
}
}
} }
public void leftCluster() { public void leftCluster() {
// Offer the service when not running in a cluster // Do nothing. An unavailable presence will be created for occupants hosted in other cluster nodes.
enableService(true, false);
//TODO Do not mess with service enablement! :)
//TODO Send unavailable presences of leaving remote occupants to LOCAL occupants
//TODO Remove rooms with no occupants (should happen with previous step)?
} }
public void leftCluster(byte[] nodeID) { public void leftCluster(byte[] nodeID) {
//TODO Send unavailable presences of leaving remote occupants to LOCAL occupants // Do nothing. An unavailable presence will be created for occupants hosted in the leaving cluster node.
//TODO Remove rooms with no occupants (should happen with previous step)?
} }
public void markedAsSeniorClusterMember() { public void markedAsSeniorClusterMember() {
// Offer the service since we are the senior cluster member // Do nothing
enableService(true, false);
} }
public Iterator<DiscoServerItem> getItems() { public Iterator<DiscoServerItem> getItems() {
...@@ -1045,7 +1163,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -1045,7 +1163,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
else if (name != null && node == null) { else if (name != null && node == null) {
// Answer the features of a given room // Answer the features of a given room
// TODO lock the room while gathering this info???
MUCRoom room = getChatRoom(name); MUCRoom room = getChatRoom(name);
if (room != null && canDiscoverRoom(room)) { if (room != null && canDiscoverRoom(room)) {
features.add("http://jabber.org/protocol/muc"); features.add("http://jabber.org/protocol/muc");
...@@ -1089,7 +1206,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -1089,7 +1206,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
public XDataFormImpl getExtendedInfo(String name, String node, JID senderJID) { public XDataFormImpl getExtendedInfo(String name, String node, JID senderJID) {
if (name != null && node == null) { if (name != null && node == null) {
// Answer the extended info of a given room // Answer the extended info of a given room
// TODO lock the room while gathering this info???
MUCRoom room = getChatRoom(name); MUCRoom room = getChatRoom(name);
if (room != null && canDiscoverRoom(room)) { if (room != null && canDiscoverRoom(room)) {
XDataFormImpl dataForm = new XDataFormImpl(DataForm.TYPE_RESULT); XDataFormImpl dataForm = new XDataFormImpl(DataForm.TYPE_RESULT);
...@@ -1322,7 +1438,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -1322,7 +1438,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
public double sample() { public double sample() {
return getNumberConnectedUsers(); return getNumberConnectedUsers(false);
} }
}; };
StatisticsManager.getInstance().addStatistic(usersStatKey, statistic); StatisticsManager.getInstance().addStatistic(usersStatKey, statistic);
...@@ -1348,6 +1464,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -1348,6 +1464,7 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
public double sample() { public double sample() {
// TODO Get these value from the other cluster nodes
return inMessages.getAndSet(0); return inMessages.getAndSet(0);
} }
}; };
......
...@@ -73,8 +73,7 @@ public class ClientRoute implements Cacheable, Externalizable { ...@@ -73,8 +73,7 @@ public class ClientRoute implements Cacheable, Externalizable {
nodeID = XMPPServer.getInstance().getNodeID(); nodeID = XMPPServer.getInstance().getNodeID();
} }
else { else {
// TODO Keep singleton instances in NodeID nodeID = NodeID.getInstance(bytes);
nodeID = new NodeID(bytes);
} }
available = ExternalizableUtil.getInstance().readBoolean(in); available = ExternalizableUtil.getInstance().readBoolean(in);
} }
......
...@@ -134,6 +134,16 @@ public class CacheFactory { ...@@ -134,6 +134,16 @@ public class CacheFactory {
} }
} }
/**
* Returns a byte[] that uniquely identifies this senior cluster member or <tt>null</tt>
* when not in a cluster.
*
* @return a byte[] that uniquely identifies this senior cluster member or null when not in a cluster.
*/
public static byte[] getSeniorClusterMemberID() {
return cacheFactoryStrategy.getSeniorClusterMemberID();
}
/** /**
* Returns true if this member is the senior member in the cluster. If clustering * Returns true if this member is the senior member in the cluster. If clustering
* is not enabled, this method will also return true. This test is useful for * is not enabled, this method will also return true. This test is useful for
......
...@@ -52,6 +52,14 @@ public interface CacheFactoryStrategy { ...@@ -52,6 +52,14 @@ public interface CacheFactoryStrategy {
*/ */
boolean isSeniorClusterMember(); boolean isSeniorClusterMember();
/**
* Returns a byte[] that uniquely identifies this senior cluster member or <tt>null</tt>
* when not in a cluster.
*
* @return a byte[] that uniquely identifies this senior cluster member or null when not in a cluster.
*/
byte[] getSeniorClusterMemberID();
/** /**
* Returns a byte[] that uniquely identifies this member within the cluster or <tt>null</tt> * Returns a byte[] that uniquely identifies this member within the cluster or <tt>null</tt>
* when not in a cluster. * when not in a cluster.
......
...@@ -159,6 +159,10 @@ public class DefaultLocalCacheStrategy implements CacheFactoryStrategy { ...@@ -159,6 +159,10 @@ public class DefaultLocalCacheStrategy implements CacheFactoryStrategy {
return true; return true;
} }
public byte[] getSeniorClusterMemberID() {
return null;
}
public byte[] getClusterMemberID() { public byte[] getClusterMemberID() {
return new byte[0]; return new byte[0];
} }
......
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
- Use is subject to license terms. - Use is subject to license terms.
--%> --%>
<%@ page import="org.jivesoftware.util.ParamUtils, <%@ page import="org.jivesoftware.openfire.muc.MUCRole,
org.jivesoftware.openfire.muc.MUCRole,
org.jivesoftware.openfire.muc.MUCRoom, org.jivesoftware.openfire.muc.MUCRoom,
org.jivesoftware.util.ParamUtils,
java.net.URLEncoder, java.net.URLEncoder,
java.text.DateFormat" java.text.DateFormat"
errorPage="error.jsp" errorPage="error.jsp"
...@@ -87,7 +87,7 @@ ...@@ -87,7 +87,7 @@
<tbody> <tbody>
<% for (MUCRole role : room.getOccupants()) { %> <% for (MUCRole role : room.getOccupants()) { %>
<tr> <tr>
<td><%= role.getChatUser().getAddress() %></td> <td><%= role.getUserAddress() %></td>
<td><%= role.getNickname() %></td> <td><%= role.getNickname() %></td>
<td><%= role.getRole() %></td> <td><%= role.getRole() %></td>
<td><%= role.getAffiliation() %></td> <td><%= role.getAffiliation() %></td>
......
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