Commit 0dfb2b24 authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gaston

Persistent rooms will always remain in memory. RoomSurrogates are no longer necessary.


git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@459 b35dd754-fafc-0310-a699-88a17e54d16e
parent 4b45689b
......@@ -493,6 +493,17 @@ public class IQOwnerHandler {
presences.addAll(room.addMember(jid, null, senderRole));
}
// Destroy the room if the room is no longer persistent and there are no occupants in
// the room
if (!room.isPersistent() && room.getOccupantsCount() == 0) {
try {
room.destroyRoom(null, null);
}
catch (UnauthorizedException e) {
// Do nothing.
}
}
}
finally {
room.lock.writeLock().unlock();
......
......@@ -15,9 +15,7 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Date;
import java.util.*;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.messenger.muc.MUCRole;
......@@ -40,11 +38,6 @@ import org.jivesoftware.util.StringUtils;
*/
public class MUCPersistenceManager {
private static final String LOAD_ROOM_SURROGATES =
"SELECT roomID, creationDate, modificationDate, name, naturalName, description, " +
"canChangeSubject, maxUsers, moderated, invitationRequired, canInvite, " +
"password, canDiscoverJID, logEnabled, subject, rolesToBroadcast " +
"FROM mucRoom WHERE inMemory=0 and publicRoom=1";
private static final String GET_RESERVED_NAME =
"SELECT nickname FROM mucMember WHERE roomID=? AND jid=?";
private static final String LOAD_ROOM =
......@@ -56,7 +49,16 @@ public class MUCPersistenceManager {
"SELECT jid,affiliation FROM mucAffiliation WHERE roomID=?";
private static final String LOAD_MEMBERS =
"SELECT jid, nickname FROM mucMember WHERE roomID=?";
private static final String UPDATE_ROOM =
private static final String LOAD_ALL_ROOMS =
"SELECT roomID, creationDate, modificationDate, name, naturalName, description, " +
"canChangeSubject, maxUsers, publicRoom, moderated, invitationRequired, canInvite, " +
"password, canDiscoverJID, logEnabled, subject, rolesToBroadcast " +
"FROM mucRoom";
private static final String LOAD_ALL_AFFILIATIONS =
"SELECT roomID,jid,affiliation FROM mucAffiliation";
private static final String LOAD_ALL_MEMBERS =
"SELECT roomID,jid, nickname FROM mucMember";
private static final String UPDATE_ROOM =
"UPDATE mucRoom SET modificationDate=?, naturalName=?, description=?, " +
"canChangeSubject=?, maxUsers=?, publicRoom=?, moderated=?, invitationRequired=?, " +
"canInvite=?, password=?, canDiscoverJID=?, logEnabled=?, rolesToBroadcast=?, " +
......@@ -68,10 +70,6 @@ public class MUCPersistenceManager {
"lastActiveDate, inMemory) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
private static final String UPDATE_SUBJECT =
"UPDATE mucRoom SET subject=? WHERE roomID=?";
private static final String UPDATE_IN_MEMORY =
"UPDATE mucRoom SET lastActiveDate=?, inMemory=? WHERE roomID=?";
private static final String RESET_IN_MEMORY =
"UPDATE mucRoom SET lastActiveDate=?, inMemory=0 WHERE inMemory=1";
private static final String DELETE_ROOM =
"DELETE FROM mucRoom WHERE roomID=?";
private static final String DELETE_AFFILIATIONS =
......@@ -95,61 +93,6 @@ public class MUCPersistenceManager {
"INSERT INTO mucConversationLog (roomID,sender,nickname,time,subject,body) " +
"VALUES (?,?,?,?,?,?)";
public static List<MUCPersistentRoomSurrogate> getRoomSurrogates(MultiUserChatServer chatserver,
PacketRouter packetRouter) {
Connection con = null;
PreparedStatement pstmt = null;
List<MUCPersistentRoomSurrogate> answer = new ArrayList<MUCPersistentRoomSurrogate>();
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_ROOM_SURROGATES);
ResultSet rs = pstmt.executeQuery();
MUCPersistentRoomSurrogate room = null;
while (rs.next()) {
room = new MUCPersistentRoomSurrogate(chatserver, rs.getString(4), packetRouter);
room.setID(rs.getLong(1));
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.setNaturalLanguageName(rs.getString(5));
room.setDescription(rs.getString(6));
room.setCanOccupantsChangeSubject(rs.getInt(7) == 1 ? true : false);
room.setMaxUsers(rs.getInt(8));
room.setModerated(rs.getInt(9) == 1 ? true : false);
room.setInvitationRequiredToEnter(rs.getInt(10) == 1 ? true : false);
room.setCanOccupantsInvite(rs.getInt(11) == 1 ? true : false);
room.setPassword(rs.getString(12));
room.setCanAnyoneDiscoverJID(rs.getInt(13) == 1 ? true : false);
room.setLogEnabled(rs.getInt(14) == 1 ? true : false);
room.setSubject(rs.getString(15));
List rolesToBroadcast = new ArrayList();
String roles = Integer.toBinaryString(rs.getInt(16));
if (roles.charAt(0) == '1') {
rolesToBroadcast.add("moderator");
}
if (roles.length() > 1 && roles.charAt(1) == '1') {
rolesToBroadcast.add("participant");
}
if (roles.length() > 2 && roles.charAt(2) == '1') {
rolesToBroadcast.add("visitor");
}
room.setRolesToBroadcastPresence(rolesToBroadcast);
answer.add(room);
}
rs.close();
pstmt.close();
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) { Log.error(e); }
try { if (con != null) con.close(); }
catch (Exception e) { Log.error(e); }
}
return answer;
}
/**
* Returns the reserved room nickname for the bare JID in a given room or null if none.
*
......@@ -273,6 +216,10 @@ public class MUCPersistenceManager {
}
}
rs.close();
// Set now that the room's configuration is updated in the database. Note: We need to
// set this now since otherwise the room's affiliations will be saved to the database
// "again" while adding them to the room!
room.setSavedToDB(true);
}
catch (SQLException sqle) {
Log.error(sqle);
......@@ -392,50 +339,97 @@ public class MUCPersistenceManager {
}
/**
* Updates the in-memmory status of the room in the database.
*
* @param room the room to update its in-memory status.
* @param inMemory boolean that indicates whether the room is available in memory or not.
* Loads all the rooms from the database. This query will be executed only when
* the service is started up.
*
* @return a collection with all the persistent rooms.
*/
public static void updateRoomInMemory(MUCRoom room, boolean inMemory) {
if (!room.isPersistent() || !room.wasSavedToDB()) {
return;
}
public static Collection<MUCRoom> loadRoomsFromDB(MultiUserChatServer chatserver,
PacketRouter packetRouter) {
Connection con = null;
PreparedStatement pstmt = null;
Map<Long,MUCRoom> rooms = new HashMap<Long,MUCRoom>();
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_IN_MEMORY);
pstmt.setString(1, StringUtils.dateToMillis(new Date()));
pstmt.setBoolean(2, inMemory);
pstmt.setLong(3, room.getID());
pstmt.executeUpdate();
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
try { if (pstmt != null) pstmt.close(); }
catch (Exception e) { Log.error(e); }
try { if (con != null) con.close(); }
catch (Exception e) { Log.error(e); }
}
}
pstmt = con.prepareStatement(LOAD_ALL_ROOMS);
ResultSet rs = pstmt.executeQuery();
MUCRoom room = null;
while (rs.next()) {
room = new MUCRoomImpl(chatserver, rs.getString(4), packetRouter);
room.setID(rs.getLong(1));
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.setNaturalLanguageName(rs.getString(5));
room.setDescription(rs.getString(6));
room.setCanOccupantsChangeSubject(rs.getInt(7) == 1 ? true : false);
room.setMaxUsers(rs.getInt(8));
room.setPublicRoom(rs.getInt(9) == 1 ? true : false);
room.setModerated(rs.getInt(10) == 1 ? true : false);
room.setInvitationRequiredToEnter(rs.getInt(11) == 1 ? true : false);
room.setCanOccupantsInvite(rs.getInt(12) == 1 ? true : false);
room.setPassword(rs.getString(13));
room.setCanAnyoneDiscoverJID(rs.getInt(14) == 1 ? true : false);
room.setLogEnabled(rs.getInt(15) == 1 ? true : false);
room.setSubject(rs.getString(16));
List rolesToBroadcast = new ArrayList();
String roles = Integer.toBinaryString(rs.getInt(17));
if (roles.charAt(0) == '1') {
rolesToBroadcast.add("moderator");
}
if (roles.length() > 1 && roles.charAt(1) == '1') {
rolesToBroadcast.add("participant");
}
if (roles.length() > 2 && roles.charAt(2) == '1') {
rolesToBroadcast.add("visitor");
}
room.setRolesToBroadcastPresence(rolesToBroadcast);
room.setPersistent(true);
rooms.put(room.getID(), room);
}
rs.close();
pstmt.close();
/**
* Updates the in-memmory status of ALL the rooms in the database to false. This is necessary
* in case the Multi-User Chat service went down unexpectedly. This query will be executed when
* the service is starting up (again).
*/
public static void resetRoomInMemory() {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(RESET_IN_MEMORY);
pstmt.setString(1, StringUtils.dateToMillis(new Date()));
pstmt.executeUpdate();
pstmt = con.prepareStatement(LOAD_ALL_AFFILIATIONS);
rs = pstmt.executeQuery();
while (rs.next()) {
String jid = rs.getString(2);
int affiliation = rs.getInt(3);
room = rooms.get(rs.getLong(1));
try {
switch (affiliation) {
case MUCRole.OWNER:
room.addOwner(jid, room.getRole());
break;
case MUCRole.ADMINISTRATOR:
room.addAdmin(jid, room.getRole());
break;
case MUCRole.OUTCAST:
room.addOutcast(jid, null, room.getRole());
break;
default:
Log.error("Unkown affiliation value " + affiliation + " for user "
+ jid + " in persistent room " + room.getID());
}
}
catch (Exception e) {
Log.error(e);
}
}
rs.close();
pstmt.close();
pstmt = con.prepareStatement(LOAD_ALL_MEMBERS);
rs = pstmt.executeQuery();
while (rs.next()) {
room = rooms.get(rs.getLong(1));
try {
room.addMember(rs.getString(2), rs.getString(3), room.getRole());
}
catch (Exception e) {
Log.error(e);
}
}
rs.close();
}
catch (SQLException sqle) {
Log.error(sqle);
......@@ -446,6 +440,14 @@ public class MUCPersistenceManager {
try { if (con != null) con.close(); }
catch (Exception e) { Log.error(e); }
}
// Set now that the room's configuration is updated in the database. Note: We need to
// set this now since otherwise the room's affiliations will be saved to the database
// "again" while adding them to the room!
for (MUCRoom room : rooms.values()) {
room.setSavedToDB(true);
}
return rooms.values();
}
/**
......
......@@ -172,8 +172,8 @@ public class MUCRoomImpl implements MUCRoom {
private boolean publicRoom = true;
/**
* Persistent rooms are saved to the database so that when the last occupant leaves the room,
* the room is removed from memory but it's configuration is saved in the database.
* 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 = false;
......@@ -279,17 +279,6 @@ public class MUCRoomImpl implements MUCRoom {
rolesToBroadcastPresence.add("moderator");
rolesToBroadcastPresence.add("participant");
rolesToBroadcastPresence.add("visitor");
// If the room is persistent load the configuration values from the DB
try {
MUCPersistenceManager.loadFromDB(this);
if (this.isPersistent()) {
this.savedToDB = true;
this.roomLocked = false;
}
}
catch (IllegalArgumentException e) {
// Do nothing. The room does not exist.
}
}
public String getName() {
......@@ -578,7 +567,9 @@ public class MUCRoomImpl implements MUCRoom {
// "unavailable" from the room to the owner including a <destroy/> element and reason
// (if provided) as defined under the "Destroying a Room" use case.
if (occupants.isEmpty()) {
// 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();
server.removeChatRoom(name);
......@@ -1631,6 +1622,11 @@ public class MUCRoomImpl implements MUCRoom {
public void setSavedToDB(boolean saved) {
this.savedToDB = saved;
if (saved) {
// Unlock the room now. This is necessary only when a persistent room has been
// retrieved from the database.
this.roomLocked = false;
}
}
public void setPersistent(boolean persistent) {
......
......@@ -91,13 +91,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
*/
private Map<String,MUCRoom> rooms = new ConcurrentHashMap<String,MUCRoom>();
/**
* Cache for the persistent room surrogates. There will be a persistent room surrogate for each
* persistent room that has not been loaded into memory. Whenever a persistent room is loaded or
* unloaded from memory the cache is updated.
*/
private Cache persistentRoomSurrogateCache;
/**
* chat users managed by this manager, table: key user jid (XMPPAddress); value ChatUser
*/
......@@ -164,13 +157,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
historyStrategy = new HistoryStrategy(null);
}
private void initializeCaches() {
// create cache - no size limit and expires after one hour of being idle
persistentRoomSurrogateCache = new Cache("Room Surrogates", -1, JiveConstants.HOUR);
// warm-up cache now to avoid a delay responding the first disco request
populateRoomSurrogateCache();
}
/**
* Probes the presence of any user who's last packet was sent more than 5 minute ago.
*/
......@@ -269,8 +255,14 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
room = rooms.get(roomName.toLowerCase());
if (room == null) {
room = new MUCRoomImpl(this, roomName, router);
// Check whether the room was just created or loaded from the database
if (!room.isPersistent()) {
// If the room is persistent load the configuration values from the DB
try {
// 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)
MUCPersistenceManager.loadFromDB(room);
}
catch (IllegalArgumentException e) {
// The room does not exist so check for creation permissions
// Room creation is always allowed for sysadmin
if (isRoomCreationRestricted() &&
!sysadmins.contains(userjid.toBareStringPrep())) {
......@@ -283,13 +275,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
room.addFirstOwner(userjid.toBareStringPrep());
}
else {
// The room was loaded from the database and is now available in memory.
// Update in the database that the room is now available in memory
MUCPersistenceManager.updateRoomInMemory(room, true);
// Remove the surrogate of the room (if any) from the surrogates cache
persistentRoomSurrogateCache.remove(room.getName());
}
rooms.put(roomName.toLowerCase(), room);
}
}
......@@ -297,39 +282,11 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
}
public MUCRoom getChatRoom(String roomName) {
MUCRoom answer = rooms.get(roomName.toLowerCase());
// If the room is not in memory check if there exists a surrogate of the room. There
// will be a surrogate if and only if exists an room in the database for the requested
// room name
if (answer == null) {
synchronized (persistentRoomSurrogateCache) {
if (persistentRoomSurrogateCache.size() == 0) {
populateRoomSurrogateCache();
}
}
answer = (MUCRoom) persistentRoomSurrogateCache.get(roomName);
}
return answer;
return rooms.get(roomName.toLowerCase());
}
public List<MUCRoom> getChatRooms() {
List<MUCRoom> answer = new ArrayList<MUCRoom>(rooms.size() * 2);
synchronized (rooms) {
answer.addAll(rooms.values());
synchronized (persistentRoomSurrogateCache) {
if (persistentRoomSurrogateCache.size() == 0) {
populateRoomSurrogateCache();
}
}
answer.addAll(persistentRoomSurrogateCache.values());
}
return answer;
}
private void populateRoomSurrogateCache() {
for (MUCRoom room : MUCPersistenceManager.getRoomSurrogates(this, router)) {
persistentRoomSurrogateCache.put(room.getName(), room);
}
return new ArrayList<MUCRoom>(rooms.values());
}
public boolean hasChatRoom(String roomName) {
......@@ -341,18 +298,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
if (room != null) {
final long chatLength = room.getChatLength();
totalChatTime += chatLength;
// Update the database to indicate that the room is no longer in memory (only if the
// room is persistent
MUCPersistenceManager.updateRoomInMemory(room, false);
// Clear the surrogates cache if the room thas is being removed from memory is
// persistent
if (room.isPersistent()) {
persistentRoomSurrogateCache.clear();
}
else {
// Just force to expire old entries since the cache doesn't have a clean-up thread
persistentRoomSurrogateCache.size();
}
}
}
......@@ -577,10 +522,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
// Log the room conversations every 5 minutes after a 5 minutes server startup delay
// (default values)
timer.schedule(new LogConversationTask(), LOG_TIMEOUT, LOG_TIMEOUT);
// Update the DB to indicate that no room is in-memory. This may be necessary when the
// server went down unexpectedly
MUCPersistenceManager.resetRoomInMemory();
initializeCaches();
}
public void start() {
......@@ -591,6 +532,10 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
params.clear();
params.add(chatServiceName);
Log.info(LocaleUtils.getLocalizedString("startup.starting.muc", params));
// Load all the persistent rooms to memory
for (MUCRoom room : MUCPersistenceManager.loadRoomsFromDB(this, router)) {
rooms.put(room.getName().toLowerCase(), room);
}
}
public void stop() {
......@@ -823,24 +768,6 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
answer.add(item);
}
}
// Load the room surrogates for persistent rooms that aren't in memory (if the cache
// is still empty)
synchronized(persistentRoomSurrogateCache) {
if (persistentRoomSurrogateCache.size() == 0) {
populateRoomSurrogateCache();
}
}
// Add items for each room surrogate (persistent room that is not in memory at
// the moment)
MUCRoom room;
for (Iterator it=persistentRoomSurrogateCache.values().iterator(); it.hasNext();) {
room = (MUCRoom)it.next();
item = DocumentHelper.createElement("item");
item.addAttribute("jid", room.getRole().getRoleAddress().toStringPrep());
item.addAttribute("name", room.getNaturalLanguageName());
answer.add(item);
}
}
else if (name != null && node == null) {
// Answer the room occupants as items if that info is publicly available
......
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