MUCPersistenceManager.java 60 KB
Newer Older
1 2 3 4 5
/**
 * $RCSfile$
 * $Revision: 1623 $
 * $Date: 2005-07-12 18:40:57 -0300 (Tue, 12 Jul 2005) $
 *
6
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
7
 *
8 9 10 11 12 13 14 15 16 17 18
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
19 20
 */

21
package org.jivesoftware.openfire.muc.spi;
22

23
import java.math.BigInteger;
24 25 26 27
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
28
import java.util.*;
29 30
import java.util.concurrent.ConcurrentHashMap;

31
import org.jivesoftware.database.DbConnectionManager;
32
import org.jivesoftware.openfire.PacketRouter;
33
import org.jivesoftware.openfire.XMPPServer;
34
import org.jivesoftware.openfire.group.GroupJID;
35
import org.jivesoftware.openfire.muc.*;
36
import org.jivesoftware.util.JiveGlobals;
37
import org.jivesoftware.util.StringUtils;
38 39
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
40
import org.xmpp.packet.JID;
41

42 43 44 45 46 47 48 49 50 51 52 53 54
/**
 * A manager responsible for ensuring room persistence. There are different ways to make a room 
 * persistent. The first attempt will be to save the room in a relation database. If for some reason
 * the room can't be saved in the database an alternative repository will be used to save the room
 * such as XML files.<p>
 * 
 * After the problem with the database has been solved, the information saved in the XML files will
 * be moved to the database.
 *
 * @author Gaston Dombiak
 */
public class MUCPersistenceManager {

55
	private static final Logger Log = LoggerFactory.getLogger(MUCPersistenceManager.class);
56 57 58
	
	// property name for optional number of days to limit persistent MUC history during reload (OF-764)
	private static final String MUC_HISTORY_RELOAD_LIMIT = "xmpp.muc.history.reload.limit";
59

60
    private static final String GET_RESERVED_NAME =
61
        "SELECT nickname FROM ofMucMember WHERE roomID=? AND jid=?";
62 63 64
    private static final String LOAD_ROOM =
        "SELECT roomID, creationDate, modificationDate, naturalName, description, lockedDate, " +
        "emptyDate, canChangeSubject, maxUsers, publicRoom, moderated, membersOnly, canInvite, " +
65
        "roomPassword, canDiscoverJID, logEnabled, subject, rolesToBroadcast, useReservedNick, " +
66
        "canChangeNick, canRegister FROM ofMucRoom WHERE serviceID=? AND name=?";
67
    private static final String LOAD_AFFILIATIONS =
68
        "SELECT jid, affiliation FROM ofMucAffiliation WHERE roomID=?";
69
    private static final String LOAD_MEMBERS =
70
        "SELECT jid, nickname FROM ofMucMember WHERE roomID=?";
71
    private static final String LOAD_HISTORY =
72
        "SELECT sender, nickname, logTime, subject, body FROM ofMucConversationLog " +
73
        "WHERE logTime>? AND roomID=? AND (nickname IS NOT NULL OR subject IS NOT NULL) ORDER BY logTime";
74 75 76
    private static final String LOAD_ALL_ROOMS =
        "SELECT roomID, creationDate, modificationDate, name, naturalName, description, " +
        "lockedDate, emptyDate, canChangeSubject, maxUsers, publicRoom, moderated, membersOnly, " +
77
        "canInvite, roomPassword, canDiscoverJID, logEnabled, subject, rolesToBroadcast, " +
78
        "useReservedNick, canChangeNick, canRegister " +
79
        "FROM ofMucRoom WHERE serviceID=? AND (emptyDate IS NULL or emptyDate > ?)";
80
    private static final String LOAD_ALL_AFFILIATIONS =
81 82
        "SELECT ofMucAffiliation.roomID,ofMucAffiliation.jid,ofMucAffiliation.affiliation " +
        "FROM ofMucAffiliation,ofMucRoom WHERE ofMucAffiliation.roomID = ofMucRoom.roomID AND ofMucRoom.serviceID=?";
83
    private static final String LOAD_ALL_MEMBERS =
84 85
        "SELECT ofMucMember.roomID,ofMucMember.jid,ofMucMember.nickname FROM ofMucMember,ofMucRoom " +
        "WHERE ofMucMember.roomID = ofMucRoom.roomID AND ofMucRoom.serviceID=?";
86
    private static final String LOAD_ALL_HISTORY =
87 88 89 90 91
        "SELECT ofMucConversationLog.roomID, ofMucConversationLog.sender, ofMucConversationLog.nickname, " +
        "ofMucConversationLog.logTime, ofMucConversationLog.subject, ofMucConversationLog.body FROM " +
        "ofMucConversationLog, ofMucRoom WHERE ofMucConversationLog.roomID = ofMucRoom.roomID AND " +
        "ofMucRoom.serviceID=? AND ofMucConversationLog.logTime>? AND (ofMucConversationLog.nickname IS NOT NULL " +
        "OR ofMucConversationLog.subject IS NOT NULL) ORDER BY ofMucConversationLog.logTime";
92
    private static final String UPDATE_ROOM =
93
        "UPDATE ofMucRoom SET modificationDate=?, naturalName=?, description=?, " +
94
        "canChangeSubject=?, maxUsers=?, publicRoom=?, moderated=?, membersOnly=?, " +
95
        "canInvite=?, roomPassword=?, canDiscoverJID=?, logEnabled=?, rolesToBroadcast=?, " +
96 97
        "useReservedNick=?, canChangeNick=?, canRegister=? WHERE roomID=?";
    private static final String ADD_ROOM = 
98
        "INSERT INTO ofMucRoom (serviceID, roomID, creationDate, modificationDate, name, naturalName, " +
99
        "description, lockedDate, emptyDate, canChangeSubject, maxUsers, publicRoom, moderated, " +
100
        "membersOnly, canInvite, roomPassword, canDiscoverJID, logEnabled, subject, " +
101
        "rolesToBroadcast, useReservedNick, canChangeNick, canRegister) VALUES (?,?,?,?,?,?,?,?,?," +
102 103
            "?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
    private static final String UPDATE_SUBJECT =
104
        "UPDATE ofMucRoom SET subject=? WHERE roomID=?";
105
    private static final String UPDATE_LOCK =
106
        "UPDATE ofMucRoom SET lockedDate=? WHERE roomID=?";
107
    private static final String UPDATE_EMPTYDATE =
108
        "UPDATE ofMucRoom SET emptyDate=? WHERE roomID=?";
109
    private static final String DELETE_ROOM =
110
        "DELETE FROM ofMucRoom WHERE roomID=?";
111
    private static final String DELETE_AFFILIATIONS =
112
        "DELETE FROM ofMucAffiliation WHERE roomID=?";
113
    private static final String DELETE_MEMBERS =
114
        "DELETE FROM ofMucMember WHERE roomID=?";
115
    private static final String ADD_MEMBER =
116
        "INSERT INTO ofMucMember (roomID,jid,nickname) VALUES (?,?,?)";
117
    private static final String UPDATE_MEMBER =
118
        "UPDATE ofMucMember SET nickname=? WHERE roomID=? AND jid=?";
119
    private static final String DELETE_MEMBER =
120
        "DELETE FROM ofMucMember WHERE roomID=? AND jid=?";
121
    private static final String ADD_AFFILIATION =
122
        "INSERT INTO ofMucAffiliation (roomID,jid,affiliation) VALUES (?,?,?)";
123
    private static final String UPDATE_AFFILIATION =
124
        "UPDATE ofMucAffiliation SET affiliation=? WHERE roomID=? AND jid=?";
125
    private static final String DELETE_AFFILIATION =
126
        "DELETE FROM ofMucAffiliation WHERE roomID=? AND jid=?";
127 128 129
    private static final String DELETE_USER_MEMBER =
        "DELETE FROM ofMucMember WHERE jid=?";
    private static final String DELETE_USER_MUCAFFILIATION =
130
        "DELETE FROM ofMucAffiliation WHERE jid=?";
131
    private static final String ADD_CONVERSATION_LOG =
132
        "INSERT INTO ofMucConversationLog (roomID,sender,nickname,logTime,subject,body) " +
133 134
        "VALUES (?,?,?,?,?,?)";

135
    /* Map of subdomains to their associated properties */
136
    private static ConcurrentHashMap<String,MUCServiceProperties> propertyMaps = new ConcurrentHashMap<>();
137

138 139 140 141 142 143 144 145 146 147
    /**
     * Returns the reserved room nickname for the bare JID in a given room or null if none.
     *
     * @param room the room where the user would like to obtain his reserved nickname. 
     * @param bareJID The bare jid of the user of which you'd like to obtain his reserved nickname.
     * @return the reserved room nickname for the bare JID or null if none.
     */
    public static String getReservedNickname(MUCRoom room, String bareJID) {
        Connection con = null;
        PreparedStatement pstmt = null;
148
        ResultSet rs = null;
149 150 151 152 153 154
        String answer = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(GET_RESERVED_NAME);
            pstmt.setLong(1, room.getID());
            pstmt.setString(2, bareJID);
155
            rs = pstmt.executeQuery();
156 157 158 159 160
            if (rs.next()) {
                answer = rs.getString(1);
            }
        }
        catch (SQLException sqle) {
161
            Log.error(sqle.getMessage(), sqle);
162 163
        }
        finally {
164
            DbConnectionManager.closeConnection(rs, pstmt, con);
165 166 167 168 169 170 171 172 173
        }
        return answer;
    }

    /**
     * Loads the room configuration from the database if the room was persistent.
     * 
     * @param room the room to load from the database if persistent
     */
174
    public static void loadFromDB(LocalMUCRoom room) {
175 176
        Connection con = null;
        PreparedStatement pstmt = null;
177
        ResultSet rs = null;
178
        try {
179
            Long serviceID = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatServiceID(room.getMUCService().getServiceName());
180 181
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(LOAD_ROOM);
182 183
            pstmt.setLong(1, serviceID);
            pstmt.setString(2, room.getName());
184
            rs = pstmt.executeQuery();
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
            if (!rs.next()) {
                throw new IllegalArgumentException("Room " + room.getName() + " was not found in the database.");
            }
            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(4));
            room.setDescription(rs.getString(5));
            room.setLockedDate(new Date(Long.parseLong(rs.getString(6).trim())));
            if (rs.getString(7) != null) {
                room.setEmptyDate(new Date(Long.parseLong(rs.getString(7).trim())));
            }
            else {
                room.setEmptyDate(null);
            }
200
            room.setCanOccupantsChangeSubject(rs.getInt(8) == 1);
201
            room.setMaxUsers(rs.getInt(9));
202 203 204 205
            room.setPublicRoom(rs.getInt(10) == 1);
            room.setModerated(rs.getInt(11) == 1);
            room.setMembersOnly(rs.getInt(12) == 1);
            room.setCanOccupantsInvite(rs.getInt(13) == 1);
206
            room.setPassword(rs.getString(14));
207 208
            room.setCanAnyoneDiscoverJID(rs.getInt(15) == 1);
            room.setLogEnabled(rs.getInt(16) == 1);
209
            room.setSubject(rs.getString(17));
210
            List<String> rolesToBroadcast = new ArrayList<>();
211
            String roles = StringUtils.zeroPadString(Integer.toBinaryString(rs.getInt(18)), 3);
212 213 214
            if (roles.charAt(0) == '1') {
                rolesToBroadcast.add("moderator");
            }
215
            if (roles.charAt(1) == '1') {
216 217
                rolesToBroadcast.add("participant");
            }
218
            if (roles.charAt(2) == '1') {
219 220 221
                rolesToBroadcast.add("visitor");
            }
            room.setRolesToBroadcastPresence(rolesToBroadcast);
222 223 224
            room.setLoginRestrictedToNickname(rs.getInt(19) == 1);
            room.setChangeNickname(rs.getInt(20) == 1);
            room.setRegistrationEnabled(rs.getInt(21) == 1);
225
            room.setPersistent(true);
226
            DbConnectionManager.fastcloseStmt(rs, pstmt);
227

228 229 230 231
            // Recreate the history only for the rooms that have the conversation logging
            // enabled
            if (room.isLogEnabled()) {
                pstmt = con.prepareStatement(LOAD_HISTORY);
232 233
                // Reload the history, using "muc.history.reload.limit" (days); defaults to 2
                int reloadLimitDays = JiveGlobals.getIntProperty(MUC_HISTORY_RELOAD_LIMIT, 2);
234
                long from = System.currentTimeMillis() - (BigInteger.valueOf(86400000).multiply(BigInteger.valueOf(reloadLimitDays))).longValue();
235 236 237 238 239 240 241 242 243
                pstmt.setString(1, StringUtils.dateToMillis(new Date(from)));
                pstmt.setLong(2, room.getID());
                rs = pstmt.executeQuery();
                while (rs.next()) {
                    String senderJID = rs.getString(1);
                    String nickname = rs.getString(2);
                    Date sentDate = new Date(Long.parseLong(rs.getString(3).trim()));
                    String subject = rs.getString(4);
                    String body = rs.getString(5);
244 245 246 247
                    room.getRoomHistory().addOldMessage(senderJID, nickname, sentDate, subject,
                            body);
                }
            }
248
            DbConnectionManager.fastcloseStmt(rs, pstmt);
249 250 251 252 253 254 255 256 257 258 259 260 261

            // If the room does not include the last subject in the history then recreate one if
            // possible
            if (!room.getRoomHistory().hasChangedSubject() && room.getSubject() != null &&
                    room.getSubject().length() > 0) {
                room.getRoomHistory().addOldMessage(room.getRole().getRoleAddress().toString(),
                        null, room.getModificationDate(), room.getSubject(), null);
            }

            pstmt = con.prepareStatement(LOAD_AFFILIATIONS);
            pstmt.setLong(1, room.getID());
            rs = pstmt.executeQuery();
            while (rs.next()) {
262 263
            	// might be a group JID
                JID affiliationJID = GroupJID.fromString(rs.getString(1));
264 265 266 267
                MUCRole.Affiliation affiliation = MUCRole.Affiliation.valueOf(rs.getInt(2));
                try {
                    switch (affiliation) {
                        case owner:
268
                            room.addOwner(affiliationJID, room.getRole());
269 270
                            break;
                        case admin:
271
                            room.addAdmin(affiliationJID, room.getRole());
272 273
                            break;
                        case outcast:
274
                            room.addOutcast(affiliationJID, null, room.getRole());
275 276 277
                            break;
                        default:
                            Log.error("Unkown affiliation value " + affiliation + " for user "
278
                                    + affiliationJID.toBareJID() + " in persistent room " + room.getID());
279 280 281
                    }
                }
                catch (Exception e) {
282
                    Log.error(e.getMessage(), e);
283 284
                }
            }
285 286
            DbConnectionManager.fastcloseStmt(rs, pstmt);
            
287 288 289 290 291
            pstmt = con.prepareStatement(LOAD_MEMBERS);
            pstmt.setLong(1, room.getID());
            rs = pstmt.executeQuery();
            while (rs.next()) {
                try {
292
                	room.addMember(new JID(rs.getString(1)), rs.getString(2), room.getRole());
293 294
                }
                catch (Exception e) {
295
                    Log.error(e.getMessage(), e);
296 297 298 299 300 301 302 303 304 305 306 307 308 309
                }
            }
            // 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);
            if (room.getEmptyDate() == null) {
                // The service process was killed somehow while the room was being used. Since
                // the room won't have occupants at this time we need to set the best date when
                // the last occupant left the room that we can
                room.setEmptyDate(new Date());
            }
        }
        catch (SQLException sqle) {
310
            Log.error(sqle.getMessage(), sqle);
311 312
        }
        finally {
313
            DbConnectionManager.closeConnection(rs, pstmt, con);
314 315 316 317 318 319 320 321
        }
    }

    /**
     * Save the room configuration to the DB.
     * 
     * @param room The room to save its configuration.
     */
322
    public static void saveToDB(LocalMUCRoom room) {
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            if (room.wasSavedToDB()) {
                pstmt = con.prepareStatement(UPDATE_ROOM);
                pstmt.setString(1, StringUtils.dateToMillis(room.getModificationDate()));
                pstmt.setString(2, room.getNaturalLanguageName());
                pstmt.setString(3, room.getDescription());
                pstmt.setInt(4, (room.canOccupantsChangeSubject() ? 1 : 0));
                pstmt.setInt(5, room.getMaxUsers());
                pstmt.setInt(6, (room.isPublicRoom() ? 1 : 0));
                pstmt.setInt(7, (room.isModerated() ? 1 : 0));
                pstmt.setInt(8, (room.isMembersOnly() ? 1 : 0));
                pstmt.setInt(9, (room.canOccupantsInvite() ? 1 : 0));
                pstmt.setString(10, room.getPassword());
                pstmt.setInt(11, (room.canAnyoneDiscoverJID() ? 1 : 0));
                pstmt.setInt(12, (room.isLogEnabled() ? 1 : 0));
                pstmt.setInt(13, marshallRolesToBroadcast(room));
                pstmt.setInt(14, (room.isLoginRestrictedToNickname() ? 1 : 0));
                pstmt.setInt(15, (room.canChangeNickname() ? 1 : 0));
                pstmt.setInt(16, (room.isRegistrationEnabled() ? 1 : 0));
                pstmt.setLong(17, room.getID());
                pstmt.executeUpdate();
            }
            else {
                pstmt = con.prepareStatement(ADD_ROOM);
350 351 352 353 354 355 356 357
                pstmt.setLong(1, XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatServiceID(room.getMUCService().getServiceName()));
                pstmt.setLong(2, room.getID());
                pstmt.setString(3, StringUtils.dateToMillis(room.getCreationDate()));
                pstmt.setString(4, StringUtils.dateToMillis(room.getModificationDate()));
                pstmt.setString(5, room.getName());
                pstmt.setString(6, room.getNaturalLanguageName());
                pstmt.setString(7, room.getDescription());
                pstmt.setString(8, StringUtils.dateToMillis(room.getLockedDate()));
358 359
                Date emptyDate = room.getEmptyDate();
                if (emptyDate == null) {
360
                    pstmt.setString(9, null);
361 362
                }
                else {
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
                    pstmt.setString(9, StringUtils.dateToMillis(emptyDate));
                }
                pstmt.setInt(10, (room.canOccupantsChangeSubject() ? 1 : 0));
                pstmt.setInt(11, room.getMaxUsers());
                pstmt.setInt(12, (room.isPublicRoom() ? 1 : 0));
                pstmt.setInt(13, (room.isModerated() ? 1 : 0));
                pstmt.setInt(14, (room.isMembersOnly() ? 1 : 0));
                pstmt.setInt(15, (room.canOccupantsInvite() ? 1 : 0));
                pstmt.setString(16, room.getPassword());
                pstmt.setInt(17, (room.canAnyoneDiscoverJID() ? 1 : 0));
                pstmt.setInt(18, (room.isLogEnabled() ? 1 : 0));
                pstmt.setString(19, room.getSubject());
                pstmt.setInt(20, marshallRolesToBroadcast(room));
                pstmt.setInt(21, (room.isLoginRestrictedToNickname() ? 1 : 0));
                pstmt.setInt(22, (room.canChangeNickname() ? 1 : 0));
                pstmt.setInt(23, (room.isRegistrationEnabled() ? 1 : 0));
379 380 381 382
                pstmt.executeUpdate();
            }
        }
        catch (SQLException sqle) {
383
            Log.error(sqle.getMessage(), sqle);
384 385
        }
        finally {
386
            DbConnectionManager.closeConnection(pstmt, con);
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
        }
    }

    /**
     * Removes the room configuration and its affiliates from the database.
     * 
     * @param room the room to remove from the database.
     */
    public static void deleteFromDB(MUCRoom room) {
        if (!room.isPersistent() || !room.wasSavedToDB()) {
            return;
        }
        Connection con = null;
        PreparedStatement pstmt = null;
        boolean abortTransaction = false;
        try {
            con = DbConnectionManager.getTransactionConnection();
            pstmt = con.prepareStatement(DELETE_AFFILIATIONS);
            pstmt.setLong(1, room.getID());
            pstmt.executeUpdate();
407
            DbConnectionManager.fastcloseStmt(pstmt);
408 409 410 411

            pstmt = con.prepareStatement(DELETE_MEMBERS);
            pstmt.setLong(1, room.getID());
            pstmt.executeUpdate();
412
            DbConnectionManager.fastcloseStmt(pstmt);
413 414 415 416 417 418 419 420 421

            pstmt = con.prepareStatement(DELETE_ROOM);
            pstmt.setLong(1, room.getID());
            pstmt.executeUpdate();

            // Update the room (in memory) to indicate the it's no longer in the database.
            room.setSavedToDB(false);
        }
        catch (SQLException sqle) {
422
            Log.error(sqle.getMessage(), sqle);
423 424 425
            abortTransaction = true;
        }
        finally {
426
            DbConnectionManager.closeStatement(pstmt);
427 428 429 430 431 432 433 434 435 436 437 438 439
            DbConnectionManager.closeTransactionConnection(con, abortTransaction);
        }
    }

    /**
     * Loads all the rooms that had occupants after a given date from the database. This query
     * will be executed only when the service is starting up.
     *
     * @param chatserver the chat server that will hold the loaded rooms.
     * @param emptyDate rooms that hadn't been used before this date won't be loaded.
     * @param packetRouter the PacketRouter that loaded rooms will use to send packets.
     * @return a collection with all the persistent rooms.
     */
440 441 442 443
    public static Collection<LocalMUCRoom> loadRoomsFromDB(MultiUserChatService chatserver, Date emptyDate, PacketRouter packetRouter) {
        Long serviceID = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatServiceID(chatserver.getServiceName());

        final Map<Long, LocalMUCRoom> rooms;
444
        try {
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
            rooms = loadRooms(serviceID, emptyDate, chatserver, packetRouter);
            loadHistory(serviceID, rooms);
            loadAffiliations(serviceID, rooms);
            loadMembers(serviceID, rooms);
        }
        catch (SQLException sqle) {
            Log.error("A database error prevented MUC rooms to be loaded from the database.", sqle);
            return Collections.emptyList();
        }

        // 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 (final MUCRoom room : rooms.values()) {
            room.setSavedToDB(true);
            if (room.getEmptyDate() == null) {
                // The service process was killed somehow while the room was being used. Since
                // the room won't have occupants at this time we need to set the best date when
                // the last occupant left the room that we can
                room.setEmptyDate(new Date());
465
            }
466
        }
467

468 469 470 471
        return rooms.values();
    }

    private static Map<Long, LocalMUCRoom> loadRooms(Long serviceID, Date emptyDate, MultiUserChatService chatserver, PacketRouter packetRouter) throws SQLException {
472
        final Map<Long, LocalMUCRoom> rooms = new HashMap<>();
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508

        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DbConnectionManager.getConnection();
            statement = connection.prepareStatement(LOAD_ALL_ROOMS);
            statement.setLong(1, serviceID);
            statement.setString(2, StringUtils.dateToMillis(emptyDate));
            resultSet = statement.executeQuery();

            while (resultSet.next()) {
                try {
                    LocalMUCRoom room = new LocalMUCRoom(chatserver, resultSet.getString(4), packetRouter);
                    room.setID(resultSet.getLong(1));
                    room.setCreationDate(new Date(Long.parseLong(resultSet.getString(2).trim()))); // creation date
                    room.setModificationDate(new Date(Long.parseLong(resultSet.getString(3).trim()))); // modification date
                    room.setNaturalLanguageName(resultSet.getString(5));
                    room.setDescription(resultSet.getString(6));
                    room.setLockedDate(new Date(Long.parseLong(resultSet.getString(7).trim())));
                    if (resultSet.getString(8) != null) {
                        room.setEmptyDate(new Date(Long.parseLong(resultSet.getString(8).trim())));
                    }
                    else {
                        room.setEmptyDate(null);
                    }
                    room.setCanOccupantsChangeSubject(resultSet.getInt(9) == 1);
                    room.setMaxUsers(resultSet.getInt(10));
                    room.setPublicRoom(resultSet.getInt(11) == 1);
                    room.setModerated(resultSet.getInt(12) == 1);
                    room.setMembersOnly(resultSet.getInt(13) == 1);
                    room.setCanOccupantsInvite(resultSet.getInt(14) == 1);
                    room.setPassword(resultSet.getString(15));
                    room.setCanAnyoneDiscoverJID(resultSet.getInt(16) == 1);
                    room.setLogEnabled(resultSet.getInt(17) == 1);
                    room.setSubject(resultSet.getString(18));
509
                    List<String> rolesToBroadcast = new ArrayList<>();
510
                    String roles = StringUtils.zeroPadString(Integer.toBinaryString(resultSet.getInt(19)), 3);
511 512 513
                    if (roles.charAt(0) == '1') {
                        rolesToBroadcast.add("moderator");
                    }
514
                    if (roles.charAt(1) == '1') {
515 516
                        rolesToBroadcast.add("participant");
                    }
517
                    if (roles.charAt(2) == '1') {
518 519 520 521 522 523 524 525 526 527
                        rolesToBroadcast.add("visitor");
                    }
                    room.setRolesToBroadcastPresence(rolesToBroadcast);
                    room.setLoginRestrictedToNickname(resultSet.getInt(20) == 1);
                    room.setChangeNickname(resultSet.getInt(21) == 1);
                    room.setRegistrationEnabled(resultSet.getInt(22) == 1);
                    room.setPersistent(true);
                    rooms.put(room.getID(), room);
                } catch (SQLException e) {
                    Log.error("A database exception prevented one particular MUC room to be loaded from the database.", e);
528
                }
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
            }
        } finally {
            DbConnectionManager.closeConnection(resultSet, statement, connection);
        }

        return rooms;
    }

    private static void loadHistory(Long serviceID, Map<Long, LocalMUCRoom> rooms) throws SQLException {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DbConnectionManager.getConnection();
            statement = connection.prepareStatement(LOAD_ALL_HISTORY);

545 546 547 548 549 550 551
            // Reload the history, using "muc.history.reload.limit" (days) if present
            long from = 0;
            String reloadLimit = JiveGlobals.getProperty(MUC_HISTORY_RELOAD_LIMIT);
            if (reloadLimit != null) {
                // if the property is defined, but not numeric, default to 2 (days)
                int reloadLimitDays = JiveGlobals.getIntProperty(MUC_HISTORY_RELOAD_LIMIT, 2);
                Log.warn("MUC history reload limit set to " + reloadLimitDays + " days");
552
                from = System.currentTimeMillis() - (BigInteger.valueOf(86400000).multiply(BigInteger.valueOf(reloadLimitDays))).longValue();
553
            }
554 555 556 557 558
            statement.setLong(1, serviceID);
            statement.setString(2, StringUtils.dateToMillis(new Date(from)));
            resultSet = statement.executeQuery();

            while (resultSet.next()) {
559
                try {
560
                    LocalMUCRoom room = rooms.get(resultSet.getLong(1));
561 562
                    // Skip to the next position if the room does not exist or if history is disabled
                    if (room == null || !room.isLogEnabled()) {
563 564 565 566 567 568 569
                        continue;
                    }
                    String senderJID = resultSet.getString(2);
                    String nickname  = resultSet.getString(3);
                    Date sentDate    = new Date(Long.parseLong(resultSet.getString(4).trim()));
                    String subject   = resultSet.getString(5);
                    String body      = resultSet.getString(6);
570
                    room.getRoomHistory().addOldMessage(senderJID, nickname, sentDate, subject, body);
571 572
                } catch (SQLException e) {
                    Log.warn("A database exception prevented the history for one particular MUC room to be loaded from the database.", e);
573 574
                }
            }
575 576 577
        } finally {
            DbConnectionManager.closeConnection(resultSet, statement, connection);
        }
578

579 580 581 582 583 584 585 586 587 588 589 590 591
        // Add the last known room subject to the room history only for those rooms that still
        // don't have in their histories the last room subject
        for (MUCRoom loadedRoom : rooms.values())
        {
            if (!loadedRoom.getRoomHistory().hasChangedSubject()
                && loadedRoom.getSubject() != null
                && loadedRoom.getSubject().length() > 0)
            {
                loadedRoom.getRoomHistory().addOldMessage(  loadedRoom.getRole().getRoleAddress().toString(),
                                                            null,
                                                            loadedRoom.getModificationDate(),
                                                            loadedRoom.getSubject(),
                                                            null);
592
            }
593 594
        }
    }
595

596 597 598 599 600 601 602 603 604 605 606
    private static void loadAffiliations(Long serviceID, Map<Long, LocalMUCRoom> rooms) throws SQLException {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = DbConnectionManager.getConnection();
            statement = connection.prepareStatement(LOAD_ALL_AFFILIATIONS);
            statement.setLong(1, serviceID);
            resultSet = statement.executeQuery();

            while (resultSet.next()) {
607
                try {
608 609 610 611 612
                    long roomID = resultSet.getLong(1);
                    LocalMUCRoom room = rooms.get(roomID);
                    // Skip to the next position if the room does not exist
                    if (room == null) {
                        continue;
613
                    }
614 615 616 617

                    final MUCRole.Affiliation affiliation = MUCRole.Affiliation.valueOf(resultSet.getInt(3));

                    final String jidValue = resultSet.getString(2);
618
                    final JID affiliationJID;
619
                    try {
620 621
                    	// might be a group JID
                        affiliationJID = GroupJID.fromString(jidValue);
622 623 624 625 626 627 628 629 630 631 632
                    } catch (IllegalArgumentException ex) {
                        Log.warn("An illegal JID ({}) was found in the database, "
                                + "while trying to load all affiliations for room "
                                + "{}. The JID is ignored."
                                , new Object[] { jidValue, roomID });
                        continue;
                    }

                    try {
                        switch (affiliation) {
                            case owner:
633
                                room.addOwner(affiliationJID, room.getRole());
634 635
                                break;
                            case admin:
636
                                room.addAdmin(affiliationJID, room.getRole());
637 638
                                break;
                            case outcast:
639
                                room.addOutcast(affiliationJID, null, room.getRole());
640 641
                                break;
                            default:
642
                                Log.error("Unknown affiliation value " + affiliation + " for user " + affiliationJID + " in persistent room " + room.getID());
643 644 645 646 647 648 649 650 651 652
                        }
                    } catch (ForbiddenException e) {
                        Log.warn("An exception prevented affiliations to be added to the room with id " + roomID, e);
                    } catch (ConflictException e) {
                        Log.warn("An exception prevented affiliations to be added to the room with id " + roomID, e);
                    } catch (NotAllowedException e) {
                        Log.warn("An exception prevented affiliations to be added to the room with id " + roomID, e);
                    }
                } catch (SQLException e) {
                    Log.error("A database exception prevented affiliations for one particular MUC room to be loaded from the database.", e);
653 654 655
                }
            }

656 657 658 659 660 661 662 663 664
        } finally {
            DbConnectionManager.closeConnection(resultSet, statement, connection);
        }
    }

    private static void loadMembers(Long serviceID, Map<Long, LocalMUCRoom> rooms) throws SQLException {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
665
        JID affiliationJID = null;
666 667 668 669 670 671 672
        try {
            connection = DbConnectionManager.getConnection();
            statement = connection.prepareStatement(LOAD_ALL_MEMBERS);
            statement.setLong(1, serviceID);
            resultSet = statement.executeQuery();

            while (resultSet.next()) {
673
                try {
674 675 676 677 678 679
                    LocalMUCRoom room = rooms.get(resultSet.getLong(1));
                    // Skip to the next position if the room does not exist
                    if (room == null) {
                        continue;
                    }
                    try {
680 681 682
                    	// might be a group JID
                    	affiliationJID = GroupJID.fromString(resultSet.getString(2));
                    	room.addMember(affiliationJID, resultSet.getString(3), room.getRole());
683 684 685 686 687 688 689
                    } catch (ForbiddenException e) {
                        Log.warn("Unable to add member to room.", e);
                    } catch (ConflictException e) {
                        Log.warn("Unable to add member to room.", e);
                    }
                } catch (SQLException e) {
                    Log.error("A database exception prevented members for one particular MUC room to be loaded from the database.", e);
690 691
                }
            }
692 693
        } finally {
            DbConnectionManager.closeConnection(resultSet, statement, connection);
694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
        }
    }

    /**
     * Updates the room's subject in the database. 
     * 
     * @param room the room to update its subject in the database.
     */
    public static void updateRoomSubject(MUCRoom room) {
        if (!room.isPersistent() || !room.wasSavedToDB()) {
            return;
        }

        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(UPDATE_SUBJECT);
            pstmt.setString(1, room.getSubject());
            pstmt.setLong(2, room.getID());
            pstmt.executeUpdate();
        }
        catch (SQLException sqle) {
717
            Log.error(sqle.getMessage(), sqle);
718 719
        }
        finally {
720
            DbConnectionManager.closeConnection(pstmt, con);
721 722 723 724 725 726 727 728
        }
    }

    /**
     * Updates the room's lock status in the database.
     *
     * @param room the room to update its lock status in the database.
     */
729
    public static void updateRoomLock(LocalMUCRoom room) {
730 731 732 733 734 735 736 737 738 739 740 741 742 743
        if (!room.isPersistent() || !room.wasSavedToDB()) {
            return;
        }

        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(UPDATE_LOCK);
            pstmt.setString(1, StringUtils.dateToMillis(room.getLockedDate()));
            pstmt.setLong(2, room.getID());
            pstmt.executeUpdate();
        }
        catch (SQLException sqle) {
744
            Log.error(sqle.getMessage(), sqle);
745 746
        }
        finally {
747
            DbConnectionManager.closeConnection(pstmt, con);
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
        }
    }

    /**
     * Updates the room's lock status in the database.
     *
     * @param room the room to update its lock status in the database.
     */
    public static void updateRoomEmptyDate(MUCRoom room) {
        if (!room.isPersistent() || !room.wasSavedToDB()) {
            return;
        }

        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(UPDATE_EMPTYDATE);
            Date emptyDate = room.getEmptyDate();
            if (emptyDate == null) {
                pstmt.setString(1, null);
            }
            else {
                pstmt.setString(1, StringUtils.dateToMillis(emptyDate));
            }
            pstmt.setLong(2, room.getID());
            pstmt.executeUpdate();
        }
        catch (SQLException sqle) {
777
            Log.error(sqle.getMessage(), sqle);
778 779
        }
        finally {
780
            DbConnectionManager.closeConnection(pstmt, con);
781 782 783 784 785 786 787 788
        }
    }

    /**
     * Update the DB with the new affiliation of the user in the room. The new information will be
     * saved only if the room is_persistent and has already been saved to the database previously.
     * 
     * @param room The room where the affiliation of the user was updated.
789
     * @param jid The bareJID of the user to update this affiliation.
790 791 792 793
     * @param nickname The reserved nickname of the user in the room or null if none.
     * @param newAffiliation the new affiliation of the user in the room.
     * @param oldAffiliation the previous affiliation of the user in the room.
     */
794
    public static void saveAffiliationToDB(MUCRoom room, JID jid, String nickname,
795 796
            MUCRole.Affiliation newAffiliation, MUCRole.Affiliation oldAffiliation)
    {
797
    	final String affiliationJid = jid.toBareJID();
798 799 800 801 802 803 804 805 806 807 808 809
        if (!room.isPersistent() || !room.wasSavedToDB()) {
            return;
        }
        if (MUCRole.Affiliation.none == oldAffiliation) {
            if (MUCRole.Affiliation.member == newAffiliation) {
                // Add the user to the members table
                Connection con = null;
                PreparedStatement pstmt = null;
                try {
                    con = DbConnectionManager.getConnection();
                    pstmt = con.prepareStatement(ADD_MEMBER);
                    pstmt.setLong(1, room.getID());
810
                    pstmt.setString(2, affiliationJid);
811 812 813 814
                    pstmt.setString(3, nickname);
                    pstmt.executeUpdate();
                }
                catch (SQLException sqle) {
815
                    Log.error(sqle.getMessage(), sqle);
816 817
                }
                finally {
818
                    DbConnectionManager.closeConnection(pstmt, con);
819 820 821 822 823 824 825 826 827 828
                }
            }
            else {
                // Add the user to the generic affiliations table
                Connection con = null;
                PreparedStatement pstmt = null;
                try {
                    con = DbConnectionManager.getConnection();
                    pstmt = con.prepareStatement(ADD_AFFILIATION);
                    pstmt.setLong(1, room.getID());
829
                    pstmt.setString(2, affiliationJid);
830 831 832 833
                    pstmt.setInt(3, newAffiliation.getValue());
                    pstmt.executeUpdate();
                }
                catch (SQLException sqle) {
834
                    Log.error(sqle.getMessage(), sqle);
835 836
                }
                finally {
837
                    DbConnectionManager.closeConnection(pstmt, con);
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
                }
            }
        }
        else {
            if (MUCRole.Affiliation.member == newAffiliation &&
                    MUCRole.Affiliation.member == oldAffiliation)
            {
                // Update the member's data in the member table.
                Connection con = null;
                PreparedStatement pstmt = null;
                try {
                    con = DbConnectionManager.getConnection();
                    pstmt = con.prepareStatement(UPDATE_MEMBER);
                    pstmt.setString(1, nickname);
                    pstmt.setLong(2, room.getID());
853
                    pstmt.setString(3, affiliationJid);
854 855 856
                    pstmt.executeUpdate();
                }
                catch (SQLException sqle) {
857
                    Log.error(sqle.getMessage(), sqle);
858 859
                }
                finally {
860
                    DbConnectionManager.closeConnection(pstmt, con);
861 862 863 864 865 866 867 868 869 870 871
                }
            }
            else if (MUCRole.Affiliation.member == newAffiliation) {
                Connection con = null;
                PreparedStatement pstmt = null;
                boolean abortTransaction = false;
                try {
                    // Remove the user from the generic affiliations table
                    con = DbConnectionManager.getTransactionConnection();
                    pstmt = con.prepareStatement(DELETE_AFFILIATION);
                    pstmt.setLong(1, room.getID());
872
                    pstmt.setString(2, affiliationJid);
873
                    pstmt.executeUpdate();
874
                    DbConnectionManager.fastcloseStmt(pstmt);
875 876 877 878

                    // Add them as a member.
                    pstmt = con.prepareStatement(ADD_MEMBER);
                    pstmt.setLong(1, room.getID());
879
                    pstmt.setString(2, affiliationJid);
880 881 882 883
                    pstmt.setString(3, nickname);
                    pstmt.executeUpdate();
                }
                catch (SQLException sqle) {
884
                    Log.error(sqle.getMessage(), sqle);
885 886 887
                    abortTransaction = true;
                }
                finally {
888
                    DbConnectionManager.closeStatement(pstmt);
889 890 891 892 893 894 895 896 897 898 899
                    DbConnectionManager.closeTransactionConnection(con, abortTransaction);
                }
            }
            else if (MUCRole.Affiliation.member == oldAffiliation) {
                Connection con = null;
                PreparedStatement pstmt = null;
                boolean abortTransaction = false;
                try {
                    con = DbConnectionManager.getTransactionConnection();
                    pstmt = con.prepareStatement(DELETE_MEMBER);
                    pstmt.setLong(1, room.getID());
900
                    pstmt.setString(2, affiliationJid);
901
                    pstmt.executeUpdate();
902
                    DbConnectionManager.fastcloseStmt(pstmt);
903 904 905

                    pstmt = con.prepareStatement(ADD_AFFILIATION);
                    pstmt.setLong(1, room.getID());
906
                    pstmt.setString(2, affiliationJid);
907 908 909 910
                    pstmt.setInt(3, newAffiliation.getValue());
                    pstmt.executeUpdate();
                }
                catch (SQLException sqle) {
911
                    Log.error(sqle.getMessage(), sqle);
912 913 914
                    abortTransaction = true;
                }
                finally {
915
                    DbConnectionManager.closeStatement(pstmt);
916 917 918 919 920 921 922 923 924 925 926 927
                    DbConnectionManager.closeTransactionConnection(con, abortTransaction);
                }
            }
            else {
                // Update the user in the generic affiliations table.
                Connection con = null;
                PreparedStatement pstmt = null;
                try {
                    con = DbConnectionManager.getConnection();
                    pstmt = con.prepareStatement(UPDATE_AFFILIATION);
                    pstmt.setInt(1, newAffiliation.getValue());
                    pstmt.setLong(2, room.getID());
928
                    pstmt.setString(3, affiliationJid);
929 930 931
                    pstmt.executeUpdate();
                }
                catch (SQLException sqle) {
932
                    Log.error(sqle.getMessage(), sqle);
933 934
                }
                finally {
935
                    DbConnectionManager.closeConnection(pstmt, con);
936 937 938 939 940 941 942 943 944
                }
            }
        }
    }

    /**
     * Removes the affiliation of the user from the DB if the room is persistent.
     * 
     * @param room The room where the affiliation of the user was removed.
945
     * @param jid The bareJID of the user to remove his affiliation.
946 947
     * @param oldAffiliation the previous affiliation of the user in the room.
     */
948
    public static void removeAffiliationFromDB(MUCRoom room, JID jid,
949 950
            MUCRole.Affiliation oldAffiliation)
    {
951
    	final String affiliationJID = jid.toBareJID();
952 953 954 955 956 957 958 959 960
        if (room.isPersistent() && room.wasSavedToDB()) {
            if (MUCRole.Affiliation.member == oldAffiliation) {
                // Remove the user from the members table
                Connection con = null;
                PreparedStatement pstmt = null;
                try {
                    con = DbConnectionManager.getConnection();
                    pstmt = con.prepareStatement(DELETE_MEMBER);
                    pstmt.setLong(1, room.getID());
961
                    pstmt.setString(2, affiliationJID);
962 963 964
                    pstmt.executeUpdate();
                }
                catch (SQLException sqle) {
965
                    Log.error(sqle.getMessage(), sqle);
966 967
                }
                finally {
968
                    DbConnectionManager.closeConnection(pstmt, con);
969 970 971 972 973 974 975 976 977 978
                }
            }
            else {
                // Remove the user from the generic affiliations table
                Connection con = null;
                PreparedStatement pstmt = null;
                try {
                    con = DbConnectionManager.getConnection();
                    pstmt = con.prepareStatement(DELETE_AFFILIATION);
                    pstmt.setLong(1, room.getID());
979
                    pstmt.setString(2, affiliationJID);
980 981 982
                    pstmt.executeUpdate();
                }
                catch (SQLException sqle) {
983
                    Log.error(sqle.getMessage(), sqle);
984 985
                }
                finally {
986
                    DbConnectionManager.closeConnection(pstmt, con);
987 988 989 990 991
                }
            }
        }
    }

992 993 994
    /**
     * Removes the affiliation of the user from the DB if ANY room that is persistent.
     *
995
     * @param affiliationJID The bareJID of the user to remove his affiliation from ALL persistent rooms.
996
     */
997
    public static void removeAffiliationFromDB(JID affiliationJID)
998 999 1000 1001 1002 1003 1004
    {
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            // Remove the user from the members table
            pstmt = con.prepareStatement(DELETE_USER_MEMBER);
1005
            pstmt.setString(1, affiliationJID.toBareJID());
1006
            pstmt.executeUpdate();
1007 1008
            DbConnectionManager.fastcloseStmt(pstmt);

1009 1010
            // Remove the user from the generic affiliations table
            pstmt = con.prepareStatement(DELETE_USER_MUCAFFILIATION);
1011
            pstmt.setString(1, affiliationJID.toBareJID());
1012 1013 1014
            pstmt.executeUpdate();
        }
        catch (SQLException sqle) {
1015
            Log.error(sqle.getMessage(), sqle);
1016 1017
        }
        finally {
1018
            DbConnectionManager.closeConnection(pstmt, con);
1019 1020 1021
        }
    }

1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047
    /**
     * Saves the conversation log entry to the database.
     * 
     * @param entry the ConversationLogEntry to save to the database.
     * @return true if the ConversationLogEntry was saved successfully to the database.
     */
    public static boolean saveConversationLogEntry(ConversationLogEntry entry) {
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(ADD_CONVERSATION_LOG);
            pstmt.setLong(1, entry.getRoomID());
            pstmt.setString(2, entry.getSender().toString());
            pstmt.setString(3, entry.getNickname());
            pstmt.setString(4, StringUtils.dateToMillis(entry.getDate()));
            pstmt.setString(5, entry.getSubject());
            pstmt.setString(6, entry.getBody());
            pstmt.executeUpdate();
            return true;
        }
        catch (SQLException sqle) {
            Log.error("Error saving conversation log entry", sqle);
            return false;
        }
        finally {
1048
            DbConnectionManager.closeConnection(pstmt, con);
1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064
        }
    }

    /**
     * Returns an integer based on the binary representation of the roles to broadcast.
     * 
     * @param room the room to marshall its roles to broadcast.
     * @return an integer based on the binary representation of the roles to broadcast.
     */
    private static int marshallRolesToBroadcast(MUCRoom room) {
        StringBuilder buffer = new StringBuilder();
        buffer.append((room.canBroadcastPresence("moderator") ? "1" : "0"));
        buffer.append((room.canBroadcastPresence("participant") ? "1" : "0"));
        buffer.append((room.canBroadcastPresence("visitor") ? "1" : "0"));
        return Integer.parseInt(buffer.toString(), 2);
    }
1065 1066 1067 1068 1069 1070 1071 1072

    /**
     * Returns a Jive property.
     *
     * @param subdomain the subdomain of the service to retrieve a property from
     * @param name the name of the property to return.
     * @return the property value specified by name.
     */
1073 1074 1075 1076 1077 1078 1079 1080
    public static String getProperty(String subdomain, String name) {    	
    	final MUCServiceProperties newProps = new MUCServiceProperties(subdomain);
    	final MUCServiceProperties oldProps = propertyMaps.putIfAbsent(subdomain, newProps);
    	if (oldProps != null) {
    		return oldProps.get(name);
    	} else {
    		return newProps.get(name);
    	}
1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092
    }

    /**
     * Returns a Jive property. If the specified property doesn't exist, the
     * <tt>defaultValue</tt> will be returned.
     *
     * @param subdomain the subdomain of the service to retrieve a property from
     * @param name the name of the property to return.
     * @param defaultValue value returned if the property doesn't exist.
     * @return the property value specified by name.
     */
    public static String getProperty(String subdomain, String name, String defaultValue) {
1093 1094 1095 1096 1097 1098
    	final String value = getProperty(subdomain, name);
    	if (value != null) {
    		return value;
    	} else {
    		return defaultValue;
    	}
1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193
    }

    /**
     * Returns an integer value Jive property. If the specified property doesn't exist, the
     * <tt>defaultValue</tt> will be returned.
     *
     * @param subdomain the subdomain of the service to retrieve a property from
     * @param name the name of the property to return.
     * @param defaultValue value returned if the property doesn't exist or was not
     *      a number.
     * @return the property value specified by name or <tt>defaultValue</tt>.
     */
    public static int getIntProperty(String subdomain, String name, int defaultValue) {
        String value = getProperty(subdomain, name);
        if (value != null) {
            try {
                return Integer.parseInt(value);
            }
            catch (NumberFormatException nfe) {
                // Ignore.
            }
        }
        return defaultValue;
    }

    /**
     * Returns a long value Jive property. If the specified property doesn't exist, the
     * <tt>defaultValue</tt> will be returned.
     *
     * @param subdomain the subdomain of the service to retrieve a property from
     * @param name the name of the property to return.
     * @param defaultValue value returned if the property doesn't exist or was not
     *      a number.
     * @return the property value specified by name or <tt>defaultValue</tt>.
     */
    public static long getLongProperty(String subdomain, String name, long defaultValue) {
        String value = getProperty(subdomain, name);
        if (value != null) {
            try {
                return Long.parseLong(value);
            }
            catch (NumberFormatException nfe) {
                // Ignore.
            }
        }
        return defaultValue;
    }

    /**
     * Returns a boolean value Jive property.
     *
     * @param subdomain the subdomain of the service to retrieve a property from
     * @param name the name of the property to return.
     * @return true if the property value exists and is set to <tt>"true"</tt> (ignoring case).
     *      Otherwise <tt>false</tt> is returned.
     */
    public static boolean getBooleanProperty(String subdomain, String name) {
        return Boolean.valueOf(getProperty(subdomain, name));
    }

    /**
     * Returns a boolean value Jive property. If the property doesn't exist, the <tt>defaultValue</tt>
     * will be returned.
     *
     * If the specified property can't be found, or if the value is not a number, the
     * <tt>defaultValue</tt> will be returned.
     *
     * @param subdomain the subdomain of the service to retrieve a property from
     * @param name the name of the property to return.
     * @param defaultValue value returned if the property doesn't exist.
     * @return true if the property value exists and is set to <tt>"true"</tt> (ignoring case).
     *      Otherwise <tt>false</tt> is returned.
     */
    public static boolean getBooleanProperty(String subdomain, String name, boolean defaultValue) {
        String value = getProperty(subdomain, name);
        if (value != null) {
            return Boolean.valueOf(value);
        }
        else {
            return defaultValue;
        }
    }

    /**
     * Return all immediate children property names of a parent Jive property as a list of strings,
     * or an empty list if there are no children. For example, given
     * the properties <tt>X.Y.A</tt>, <tt>X.Y.B</tt>, <tt>X.Y.C</tt> and <tt>X.Y.C.D</tt>, then
     * the immediate child properties of <tt>X.Y</tt> are <tt>A</tt>, <tt>B</tt>, and
     * <tt>C</tt> (<tt>C.D</tt> would not be returned using this method).<p>
     *
     * @param subdomain the subdomain of the service to retrieve a property from
     * @param parent the root "node" of the properties to retrieve
     * @return a List of all immediate children property names (Strings).
     */
    public static List<String> getPropertyNames(String subdomain, String parent) {
1194 1195 1196 1197 1198
    	MUCServiceProperties properties = new MUCServiceProperties(subdomain);
    	final MUCServiceProperties oldProps = propertyMaps.putIfAbsent(subdomain, properties);
    	if (oldProps != null) {
    		properties = oldProps;
    	} 
1199
        return new ArrayList<>(properties.getChildrenNames(parent));
1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
    }

    /**
     * Return all immediate children property values of a parent Jive property as a list of strings,
     * or an empty list if there are no children. For example, given
     * the properties <tt>X.Y.A</tt>, <tt>X.Y.B</tt>, <tt>X.Y.C</tt> and <tt>X.Y.C.D</tt>, then
     * the immediate child properties of <tt>X.Y</tt> are <tt>X.Y.A</tt>, <tt>X.Y.B</tt>, and
     * <tt>X.Y.C</tt> (the value of <tt>X.Y.C.D</tt> would not be returned using this method).<p>
     *
     * @param subdomain the subdomain of the service to retrieve a property from
     * @param parent the name of the parent property to return the children for.
     * @return all child property values for the given parent.
     */
    public static List<String> getProperties(String subdomain, String parent) {
1214 1215 1216 1217 1218
    	MUCServiceProperties properties = new MUCServiceProperties(subdomain);
    	final MUCServiceProperties oldProps = propertyMaps.putIfAbsent(subdomain, properties);
    	if (oldProps != null) {
    		properties = oldProps;
    	} 
1219 1220

        Collection<String> propertyNames = properties.getChildrenNames(parent);
1221
        List<String> values = new ArrayList<>();
1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238
        for (String propertyName : propertyNames) {
            String value = getProperty(subdomain, propertyName);
            if (value != null) {
                values.add(value);
            }
        }

        return values;
    }

    /**
     * Returns all MUC service property names.
     *
     * @param subdomain the subdomain of the service to retrieve a property from
     * @return a List of all property names (Strings).
     */
    public static List<String> getPropertyNames(String subdomain) {
1239 1240 1241 1242 1243
    	MUCServiceProperties properties = new MUCServiceProperties(subdomain);
    	final MUCServiceProperties oldProps = propertyMaps.putIfAbsent(subdomain, properties);
    	if (oldProps != null) {
    		properties = oldProps;
    	} 
1244
        return new ArrayList<>(properties.getPropertyNames());
1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263
    }

    /**
     * Sets a Jive property. If the property doesn't already exists, a new
     * one will be created.
     *
     * @param subdomain the subdomain of the service to set a property for
     * @param name the name of the property being set.
     * @param value the value of the property being set.
     */
    public static void setProperty(String subdomain, String name, String value) {
        MUCServiceProperties properties = propertyMaps.get(subdomain);
        if (properties == null) {
            properties = new MUCServiceProperties(subdomain);
        }
        properties.put(name, value);
        propertyMaps.put(subdomain, properties);
    }

1264 1265 1266 1267 1268 1269 1270 1271 1272
    public static void setLocalProperty(String subdomain, String name, String value) {
        MUCServiceProperties properties = propertyMaps.get(subdomain);
        if (properties == null) {
            properties = new MUCServiceProperties(subdomain);
        }
        properties.localPut(name, value);
        propertyMaps.put(subdomain, properties);
    }

1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304
   /**
     * Sets multiple Jive properties at once. If a property doesn't already exists, a new
     * one will be created.
     *
    * @param subdomain the subdomain of the service to set properties for
     * @param propertyMap a map of properties, keyed on property name.
     */
    public static void setProperties(String subdomain, Map<String, String> propertyMap) {
        MUCServiceProperties properties = propertyMaps.get(subdomain);
        if (properties == null) {
            properties = new MUCServiceProperties(subdomain);
        }
        properties.putAll(propertyMap);
        propertyMaps.put(subdomain, properties);
    }

    /**
     * Deletes a Jive property. If the property doesn't exist, the method
     * does nothing. All children of the property will be deleted as well.
     *
     * @param subdomain the subdomain of the service to delete a property from
     * @param name the name of the property to delete.
     */
    public static void deleteProperty(String subdomain, String name) {
        MUCServiceProperties properties = propertyMaps.get(subdomain);
        if (properties == null) {
            properties = new MUCServiceProperties(subdomain);
        }
        properties.remove(name);
        propertyMaps.put(subdomain, properties);
    }

1305 1306 1307 1308 1309 1310 1311 1312 1313
    public static void deleteLocalProperty(String subdomain, String name) {
        MUCServiceProperties properties = propertyMaps.get(subdomain);
        if (properties == null) {
            properties = new MUCServiceProperties(subdomain);
        }
        properties.localRemove(name);
        propertyMaps.put(subdomain, properties);
    }

1314 1315 1316 1317 1318 1319 1320 1321 1322
    /**
     * Resets (reloads) the properties for a specified subdomain.
     *
     * @param subdomain the subdomain of the service to reload properties for.
     */
    public static void refreshProperties(String subdomain) {
        propertyMaps.replace(subdomain, new MUCServiceProperties(subdomain));
    }
    
1323
}