1 /* 2 * Copyright (C) 2004-2008 Jive Software. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.jivesoftware.openfire.muc.spi; 18 19 import org.jivesoftware.database.DbConnectionManager; 20 import org.jivesoftware.openfire.XMPPServer; 21 import org.jivesoftware.openfire.group.GroupJID; 22 import org.jivesoftware.openfire.muc.*; 23 import org.jivesoftware.util.JiveGlobals; 24 import org.jivesoftware.util.StringUtils; 25 import org.slf4j.Logger; 26 import org.slf4j.LoggerFactory; 27 import org.xmpp.packet.JID; 28 29 import java.math.BigInteger; 30 import java.sql.*; 31 import java.util.Date; 32 import java.util.*; 33 import java.util.concurrent.ConcurrentHashMap; 34 import java.util.stream.Collectors; 35 import java.util.stream.Stream; 36 37 import static org.jivesoftware.openfire.muc.spi.FMUCMode.MasterMaster; 38 39 /** 40 * A manager responsible for ensuring room persistence. There are different ways to make a room 41 * persistent. The first attempt will be to save the room in a relation database. If for some reason 42 * the room can't be saved in the database an alternative repository will be used to save the room 43 * such as XML files.<p> 44 * 45 * After the problem with the database has been solved, the information saved in the XML files will 46 * be moved to the database. 47 * 48 * @author Gaston Dombiak 49 */ 50 public class MUCPersistenceManager { 51 52 private static final Logger Log = LoggerFactory.getLogger(MUCPersistenceManager.class); 53 54 // property name for optional number of days to limit persistent MUC history during reload (OF-764) 55 private static final String MUC_HISTORY_RELOAD_LIMIT = "xmpp.muc.history.reload.limit"; 56 57 private static final String GET_RESERVED_NAME = 58 "SELECT nickname FROM ofMucMember WHERE roomID=? AND jid=?"; 59 private static final String LOAD_ROOM = 60 "SELECT roomID, creationDate, modificationDate, naturalName, description, lockedDate, " + 61 "emptyDate, canChangeSubject, maxUsers, publicRoom, moderated, membersOnly, canInvite, " + 62 "roomPassword, canDiscoverJID, logEnabled, subject, rolesToBroadcast, useReservedNick, " + 63 "canChangeNick, canRegister, allowpm, fmucEnabled, fmucOutboundNode, fmucOutboundMode, " + 64 "fmucInboundNodes " + 65 " FROM ofMucRoom WHERE serviceID=? AND name=?"; 66 private static final String LOAD_AFFILIATIONS = 67 "SELECT jid, affiliation FROM ofMucAffiliation WHERE roomID=?"; 68 private static final String LOAD_MEMBERS = 69 "SELECT jid, nickname FROM ofMucMember WHERE roomID=?"; 70 private static final String LOAD_HISTORY = 71 "SELECT sender, nickname, logTime, subject, body, stanza FROM ofMucConversationLog " + 72 "WHERE logTime>? AND roomID=? AND (nickname IS NOT NULL OR subject IS NOT NULL) ORDER BY logTime"; 73 private static final String RELOAD_ALL_ROOMS_WITH_RECENT_ACTIVITY = 74 "SELECT roomID, creationDate, modificationDate, name, naturalName, description, " + 75 "lockedDate, emptyDate, canChangeSubject, maxUsers, publicRoom, moderated, membersOnly, " + 76 "canInvite, roomPassword, canDiscoverJID, logEnabled, subject, rolesToBroadcast, " + 77 "useReservedNick, canChangeNick, canRegister, allowpm, fmucEnabled, fmucOutboundNode, " + 78 "fmucOutboundMode, fmucInboundNodes " + 79 "FROM ofMucRoom WHERE serviceID=? AND (emptyDate IS NULL or emptyDate > ?)"; 80 private static final String LOAD_ALL_ROOMS = 81 "SELECT roomID, creationDate, modificationDate, name, naturalName, description, " + 82 "lockedDate, emptyDate, canChangeSubject, maxUsers, publicRoom, moderated, membersOnly, " + 83 "canInvite, roomPassword, canDiscoverJID, logEnabled, subject, rolesToBroadcast, " + 84 "useReservedNick, canChangeNick, canRegister, allowpm, fmucEnabled, fmucOutboundNode, " + 85 "fmucOutboundMode, fmucInboundNodes " + 86 "FROM ofMucRoom WHERE serviceID=?"; 87 private static final String COUNT_ALL_ROOMS = 88 "SELECT count(*) FROM ofMucRoom WHERE serviceID=?"; 89 private static final String LOAD_ALL_ROOM_NAMES = 90 "SELECT name FROM ofMucRoom WHERE serviceID=?"; 91 private static final String LOAD_ALL_AFFILIATIONS = 92 "SELECT ofMucAffiliation.roomID AS roomID, ofMucAffiliation.jid AS jid, ofMucAffiliation.affiliation AS affiliation " + 93 "FROM ofMucAffiliation,ofMucRoom WHERE ofMucAffiliation.roomID = ofMucRoom.roomID AND ofMucRoom.serviceID=?"; 94 private static final String LOAD_ALL_MEMBERS = 95 "SELECT ofMucMember.roomID AS roomID, ofMucMember.jid AS jid, ofMucMember.nickname AS nickname FROM ofMucMember,ofMucRoom " + 96 "WHERE ofMucMember.roomID = ofMucRoom.roomID AND ofMucRoom.serviceID=?"; 97 private static final String LOAD_ALL_HISTORY = 98 "SELECT ofMucConversationLog.roomID AS roomID, ofMucConversationLog.sender AS sender, ofMucConversationLog.nickname AS nickname, " + 99 "ofMucConversationLog.logTime AS logTime, ofMucConversationLog.subject AS subject, ofMucConversationLog.body AS body, ofMucConversationLog.stanza AS stanza FROM " + 100 "ofMucConversationLog, ofMucRoom WHERE ofMucConversationLog.roomID = ofMucRoom.roomID AND " + 101 "ofMucRoom.serviceID=? AND ofMucConversationLog.logTime>? AND (ofMucConversationLog.nickname IS NOT NULL " + 102 "OR ofMucConversationLog.subject IS NOT NULL) ORDER BY ofMucConversationLog.logTime"; 103 private static final String UPDATE_ROOM = 104 "UPDATE ofMucRoom SET modificationDate=?, naturalName=?, description=?, " + 105 "canChangeSubject=?, maxUsers=?, publicRoom=?, moderated=?, membersOnly=?, " + 106 "canInvite=?, roomPassword=?, canDiscoverJID=?, logEnabled=?, rolesToBroadcast=?, " + 107 "useReservedNick=?, canChangeNick=?, canRegister=?, allowpm=?, fmucEnabled=?, " + 108 "fmucOutboundNode=?, fmucOutboundMode=?, fmucInboundNodes=? " + 109 "WHERE roomID=?"; 110 private static final String ADD_ROOM = 111 "INSERT INTO ofMucRoom (serviceID, roomID, creationDate, modificationDate, name, naturalName, " + 112 "description, lockedDate, emptyDate, canChangeSubject, maxUsers, publicRoom, moderated, " + 113 "membersOnly, canInvite, roomPassword, canDiscoverJID, logEnabled, subject, " + 114 "rolesToBroadcast, useReservedNick, canChangeNick, canRegister, allowpm, fmucEnabled, fmucOutboundNode, " + 115 "fmucOutboundMode, fmucInboundNodes) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; 116 private static final String UPDATE_SUBJECT = 117 "UPDATE ofMucRoom SET subject=? WHERE roomID=?"; 118 private static final String UPDATE_LOCK = 119 "UPDATE ofMucRoom SET lockedDate=? WHERE roomID=?"; 120 private static final String UPDATE_EMPTYDATE = 121 "UPDATE ofMucRoom SET emptyDate=? WHERE roomID=?"; 122 private static final String DELETE_ROOM = 123 "DELETE FROM ofMucRoom WHERE roomID=?"; 124 private static final String DELETE_AFFILIATIONS = 125 "DELETE FROM ofMucAffiliation WHERE roomID=?"; 126 private static final String DELETE_MEMBERS = 127 "DELETE FROM ofMucMember WHERE roomID=?"; 128 private static final String ADD_MEMBER = 129 "INSERT INTO ofMucMember (roomID,jid,nickname) VALUES (?,?,?)"; 130 private static final String UPDATE_MEMBER = 131 "UPDATE ofMucMember SET nickname=? WHERE roomID=? AND jid=?"; 132 private static final String DELETE_MEMBER = 133 "DELETE FROM ofMucMember WHERE roomID=? AND jid=?"; 134 private static final String ADD_AFFILIATION = 135 "INSERT INTO ofMucAffiliation (roomID,jid,affiliation) VALUES (?,?,?)"; 136 private static final String UPDATE_AFFILIATION = 137 "UPDATE ofMucAffiliation SET affiliation=? WHERE roomID=? AND jid=?"; 138 private static final String DELETE_AFFILIATION = 139 "DELETE FROM ofMucAffiliation WHERE roomID=? AND jid=?"; 140 private static final String DELETE_USER_MEMBER = 141 "DELETE FROM ofMucMember WHERE jid=?"; 142 private static final String DELETE_USER_MUCAFFILIATION = 143 "DELETE FROM ofMucAffiliation WHERE jid=?"; 144 private static final String ADD_CONVERSATION_LOG = 145 "INSERT INTO ofMucConversationLog (roomID,messageID,sender,nickname,logTime,subject,body,stanza) VALUES (?,?,?,?,?,?,?,?)"; 146 147 /* Map of subdomains to their associated properties */ 148 private static ConcurrentHashMap<String,MUCServiceProperties> propertyMaps = new ConcurrentHashMap<>(); 149 150 /** 151 * Returns the reserved room nickname for the bare JID in a given room or null if none. 152 * 153 * @param room the room where the user would like to obtain his reserved nickname. 154 * @param bareJID The bare jid of the user of which you'd like to obtain his reserved nickname. 155 * @return the reserved room nickname for the bare JID or null if none. 156 */ getReservedNickname(MUCRoom room, String bareJID)157 public static String getReservedNickname(MUCRoom room, String bareJID) { 158 Connection con = null; 159 PreparedStatement pstmt = null; 160 ResultSet rs = null; 161 String answer = null; 162 try { 163 con = DbConnectionManager.getConnection(); 164 pstmt = con.prepareStatement(GET_RESERVED_NAME); 165 pstmt.setLong(1, room.getID()); 166 pstmt.setString(2, bareJID); 167 rs = pstmt.executeQuery(); 168 if (rs.next()) { 169 answer = rs.getString("nickname"); 170 } 171 } 172 catch (SQLException sqle) { 173 Log.error(sqle.getMessage(), sqle); 174 } 175 finally { 176 DbConnectionManager.closeConnection(rs, pstmt, con); 177 } 178 return answer; 179 } 180 181 /** 182 * Counts all rooms of a chat service. 183 * 184 * Note that this method will count only rooms that are persisted in the database, and can exclude in-memory rooms 185 * that are not persisted. 186 * 187 * @param service the chat service for which to return a room count. 188 * @return A room number count 189 */ countRooms(MultiUserChatService service)190 public static int countRooms(MultiUserChatService service) { 191 Log.debug("Counting rooms for service '{}' in the database.", service.getServiceName()); 192 Connection con = null; 193 PreparedStatement pstmt = null; 194 ResultSet rs = null; 195 try { 196 Long serviceID = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatServiceID(service.getServiceName()); 197 con = DbConnectionManager.getConnection(); 198 pstmt = con.prepareStatement(COUNT_ALL_ROOMS); 199 pstmt.setLong(1, serviceID); 200 rs = pstmt.executeQuery(); 201 if (!rs.next()) { 202 throw new IllegalArgumentException("Service " + service.getServiceName() + " was not found in the database."); 203 } 204 return rs.getInt(1); 205 } catch (SQLException sqle) { 206 Log.error("An exception occurred while trying to count all persisted rooms of service '{}'", service.getServiceName(), sqle); 207 return -1; 208 } 209 finally { 210 DbConnectionManager.closeConnection(rs, pstmt, con); 211 } 212 } 213 214 /** 215 * Loads the room configuration from the database if the room was persistent. 216 * 217 * @param room the room to load from the database if persistent 218 */ loadFromDB(MUCRoom room)219 public static void loadFromDB(MUCRoom room) { 220 Log.debug("Attempting to load room '{}' from the database.", room.getName()); 221 Connection con = null; 222 PreparedStatement pstmt = null; 223 ResultSet rs = null; 224 try { 225 Long serviceID = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatServiceID(room.getMUCService().getServiceName()); 226 con = DbConnectionManager.getConnection(); 227 pstmt = con.prepareStatement(LOAD_ROOM); 228 pstmt.setLong(1, serviceID); 229 pstmt.setString(2, room.getName()); 230 rs = pstmt.executeQuery(); 231 if (!rs.next()) { 232 throw new IllegalArgumentException("Room " + room.getName() + " was not found in the database."); 233 } 234 room.setID(rs.getLong("roomID")); 235 room.setCreationDate(new Date(Long.parseLong(rs.getString("creationDate").trim()))); 236 room.setModificationDate(new Date(Long.parseLong(rs.getString("modificationDate").trim()))); 237 room.setNaturalLanguageName(rs.getString("naturalName")); 238 room.setDescription(rs.getString("description")); 239 room.setLockedDate(new Date(Long.parseLong(rs.getString("lockedDate").trim()))); 240 if (rs.getString("emptyDate") != null) { 241 room.setEmptyDate(new Date(Long.parseLong(rs.getString("emptyDate").trim()))); 242 } 243 else { 244 room.setEmptyDate(null); 245 } 246 room.setCanOccupantsChangeSubject(rs.getInt("canChangeSubject") == 1); 247 room.setMaxUsers(rs.getInt("maxUsers")); 248 room.setPublicRoom(rs.getInt("publicRoom") == 1); 249 room.setModerated(rs.getInt("moderated") == 1); 250 room.setMembersOnly(rs.getInt("membersOnly") == 1); 251 room.setCanOccupantsInvite(rs.getInt("canInvite") == 1); 252 room.setPassword(rs.getString("roomPassword")); 253 room.setCanAnyoneDiscoverJID(rs.getInt("canDiscoverJID") == 1); 254 room.setLogEnabled(rs.getInt("logEnabled") == 1); 255 room.setSubject(rs.getString("subject")); 256 List<MUCRole.Role> rolesToBroadcast = new ArrayList<>(); 257 String roles = StringUtils.zeroPadString(Integer.toBinaryString(rs.getInt("rolesToBroadcast")), 3); 258 if (roles.charAt(0) == '1') { 259 rolesToBroadcast.add(MUCRole.Role.moderator); 260 } 261 if (roles.charAt(1) == '1') { 262 rolesToBroadcast.add(MUCRole.Role.participant); 263 } 264 if (roles.charAt(2) == '1') { 265 rolesToBroadcast.add(MUCRole.Role.visitor); 266 } 267 room.setRolesToBroadcastPresence(rolesToBroadcast); 268 room.setLoginRestrictedToNickname(rs.getInt("useReservedNick") == 1); 269 room.setChangeNickname(rs.getInt("canChangeNick") == 1); 270 room.setRegistrationEnabled(rs.getInt("canRegister") == 1); 271 switch (rs.getInt("allowpm")) // null returns 0. 272 { 273 default: 274 case 0: room.setCanSendPrivateMessage( "anyone" ); break; 275 case 1: room.setCanSendPrivateMessage( "participants" ); break; 276 case 2: room.setCanSendPrivateMessage( "moderators" ); break; 277 case 3: room.setCanSendPrivateMessage( "none" ); break; 278 } 279 room.setFmucEnabled(rs.getInt("fmucEnabled") == 1); 280 281 if ( rs.getString("fmucOutboundNode") != null ) { 282 final JID fmucOutboundNode = new JID(rs.getString("fmucOutboundNode")); 283 final FMUCMode fmucOutboundJoinMode; 284 switch (rs.getInt("fmucOutboundMode")) // null returns 0. 285 { 286 default: 287 case 0: fmucOutboundJoinMode = MasterMaster; break; 288 case 1: fmucOutboundJoinMode = FMUCMode.MasterSlave; break; 289 } 290 room.setFmucOutboundNode( fmucOutboundNode ); 291 room.setFmucOutboundMode( fmucOutboundJoinMode ); 292 } else { 293 room.setFmucOutboundNode( null ); 294 room.setFmucOutboundMode( null ); 295 } 296 if ( rs.getString("fmucInboundNodes") != null ) { 297 final Set<JID> fmucInboundNodes = Stream.of(rs.getString("fmucInboundNodes").split("\n")) 298 .map(String::trim) 299 .map(JID::new) 300 .collect(Collectors.toSet()); 301 // A list, which is an 'allow only on list' configuration. Note that the list can be empty (effectively: disallow all). 302 room.setFmucInboundNodes(fmucInboundNodes); 303 } else { 304 // Null: this is an 'allow all' configuration. 305 room.setFmucInboundNodes(null); 306 } 307 308 room.setPersistent(true); 309 DbConnectionManager.fastcloseStmt(rs, pstmt); 310 311 // Recreate the history only for the rooms that have the conversation logging 312 // enabled 313 if (room.isLogEnabled()) { 314 pstmt = con.prepareStatement(LOAD_HISTORY); 315 // Reload the history, using "muc.history.reload.limit" (days); defaults to 2 316 int reloadLimitDays = JiveGlobals.getIntProperty(MUC_HISTORY_RELOAD_LIMIT, 2); 317 long from = System.currentTimeMillis() - (BigInteger.valueOf(86400000).multiply(BigInteger.valueOf(reloadLimitDays))).longValue(); 318 pstmt.setString(1, StringUtils.dateToMillis(new Date(from))); 319 pstmt.setLong(2, room.getID()); 320 rs = pstmt.executeQuery(); 321 322 while (rs.next()) { 323 String senderJID = rs.getString("sender"); 324 String nickname = rs.getString("nickname"); 325 Date sentDate = new Date(Long.parseLong(rs.getString("logTime").trim())); 326 String subject = rs.getString("subject"); 327 String body = rs.getString("body"); 328 String stanza = rs.getString("stanza"); 329 room.getRoomHistory().addOldMessage(senderJID, nickname, sentDate, subject, body, stanza); 330 } 331 } 332 DbConnectionManager.fastcloseStmt(rs, pstmt); 333 334 // If the room does not include the last subject in the history then recreate one if 335 // possible 336 if (!room.getRoomHistory().hasChangedSubject() && room.getSubject() != null && 337 room.getSubject().length() > 0) { 338 room.getRoomHistory().addOldMessage(room.getRole().getRoleAddress().toString(), 339 null, room.getModificationDate(), room.getSubject(), null, null); 340 } 341 342 pstmt = con.prepareStatement(LOAD_AFFILIATIONS); 343 pstmt.setLong(1, room.getID()); 344 rs = pstmt.executeQuery(); 345 while (rs.next()) { 346 // might be a group JID 347 JID affiliationJID = GroupJID.fromString(rs.getString("jid")); 348 MUCRole.Affiliation affiliation = MUCRole.Affiliation.valueOf(rs.getInt("affiliation")); 349 try { 350 switch (affiliation) { 351 case owner: 352 room.addOwner(affiliationJID, room.getRole()); 353 break; 354 case admin: 355 room.addAdmin(affiliationJID, room.getRole()); 356 break; 357 case outcast: 358 room.addOutcast(affiliationJID, null, room.getRole()); 359 break; 360 default: 361 Log.error("Unkown affiliation value " + affiliation + " for user " 362 + affiliationJID.toBareJID() + " in persistent room " + room.getID()); 363 } 364 } 365 catch (Exception e) { 366 Log.error(e.getMessage(), e); 367 } 368 } 369 DbConnectionManager.fastcloseStmt(rs, pstmt); 370 371 pstmt = con.prepareStatement(LOAD_MEMBERS); 372 pstmt.setLong(1, room.getID()); 373 rs = pstmt.executeQuery(); 374 while (rs.next()) { 375 try { 376 room.addMember(new JID(rs.getString("jid")), rs.getString("nickname"), room.getRole()); 377 } 378 catch (Exception e) { 379 Log.error(e.getMessage(), e); 380 } 381 } 382 // Set now that the room's configuration is updated in the database. Note: We need to 383 // set this now since otherwise the room's affiliations will be saved to the database 384 // "again" while adding them to the room! 385 room.setSavedToDB(true); 386 if (room.getEmptyDate() == null) { 387 // The service process was killed somehow while the room was being used. Since 388 // the room won't have occupants at this time we need to set the best date when 389 // the last occupant left the room that we can 390 room.setEmptyDate(new Date()); 391 } 392 } 393 catch (SQLException sqle) { 394 Log.error(sqle.getMessage(), sqle); 395 } 396 finally { 397 DbConnectionManager.closeConnection(rs, pstmt, con); 398 } 399 } 400 401 /** 402 * Save the room configuration to the DB. 403 * 404 * @param room The room to save its configuration. 405 */ saveToDB(MUCRoom room)406 public static void saveToDB(MUCRoom room) { 407 Log.debug("Attempting to save room '{}' to the database.", room.getName()); 408 Connection con = null; 409 PreparedStatement pstmt = null; 410 try { 411 con = DbConnectionManager.getConnection(); 412 if (room.wasSavedToDB()) { 413 pstmt = con.prepareStatement(UPDATE_ROOM); 414 pstmt.setString(1, StringUtils.dateToMillis(room.getModificationDate())); 415 pstmt.setString(2, room.getNaturalLanguageName()); 416 pstmt.setString(3, room.getDescription()); 417 pstmt.setInt(4, (room.canOccupantsChangeSubject() ? 1 : 0)); 418 pstmt.setInt(5, room.getMaxUsers()); 419 pstmt.setInt(6, (room.isPublicRoom() ? 1 : 0)); 420 pstmt.setInt(7, (room.isModerated() ? 1 : 0)); 421 pstmt.setInt(8, (room.isMembersOnly() ? 1 : 0)); 422 pstmt.setInt(9, (room.canOccupantsInvite() ? 1 : 0)); 423 pstmt.setString(10, room.getPassword()); 424 pstmt.setInt(11, (room.canAnyoneDiscoverJID() ? 1 : 0)); 425 pstmt.setInt(12, (room.isLogEnabled() ? 1 : 0)); 426 pstmt.setInt(13, marshallRolesToBroadcast(room)); 427 pstmt.setInt(14, (room.isLoginRestrictedToNickname() ? 1 : 0)); 428 pstmt.setInt(15, (room.canChangeNickname() ? 1 : 0)); 429 pstmt.setInt(16, (room.isRegistrationEnabled() ? 1 : 0)); 430 switch (room.canSendPrivateMessage()) 431 { 432 default: 433 case "anyone": pstmt.setInt(17, 0); break; 434 case "participants": pstmt.setInt(17, 1); break; 435 case "moderators": pstmt.setInt(17, 2); break; 436 case "none": pstmt.setInt(17, 3); break; 437 } 438 pstmt.setInt(18, (room.isFmucEnabled() ? 1 : 0 )); 439 if ( room.getFmucOutboundNode() == null ) { 440 pstmt.setNull(19, Types.VARCHAR); 441 } else { 442 pstmt.setString(19, room.getFmucOutboundNode().toString()); 443 } 444 if ( room.getFmucOutboundMode() == null ) { 445 pstmt.setNull(20, Types.INTEGER); 446 } else { 447 pstmt.setInt(20, room.getFmucOutboundMode().equals(MasterMaster) ? 0 : 1); 448 } 449 450 // Store a newline-separated collection, which is an 'allow only on list' configuration. Note that the list can be empty (effectively: disallow all), or null: this is an 'allow all' configuration. 451 if (room.getFmucInboundNodes() == null) { 452 pstmt.setNull(21, Types.VARCHAR); // Null: allow all. 453 } else { 454 final String content = room.getFmucInboundNodes().stream().map(JID::toString).collect(Collectors.joining("\n")); // result potentially is an empty String, but will not be null. 455 pstmt.setString(21, content); 456 } 457 pstmt.setLong(22, room.getID()); 458 pstmt.executeUpdate(); 459 } 460 else { 461 pstmt = con.prepareStatement(ADD_ROOM); 462 pstmt.setLong(1, XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatServiceID(room.getMUCService().getServiceName())); 463 pstmt.setLong(2, room.getID()); 464 pstmt.setString(3, StringUtils.dateToMillis(room.getCreationDate())); 465 pstmt.setString(4, StringUtils.dateToMillis(room.getModificationDate())); 466 pstmt.setString(5, room.getName()); 467 pstmt.setString(6, room.getNaturalLanguageName()); 468 pstmt.setString(7, room.getDescription()); 469 pstmt.setString(8, StringUtils.dateToMillis(room.getLockedDate())); 470 Date emptyDate = room.getEmptyDate(); 471 if (emptyDate == null) { 472 pstmt.setString(9, null); 473 } 474 else { 475 pstmt.setString(9, StringUtils.dateToMillis(emptyDate)); 476 } 477 pstmt.setInt(10, (room.canOccupantsChangeSubject() ? 1 : 0)); 478 pstmt.setInt(11, room.getMaxUsers()); 479 pstmt.setInt(12, (room.isPublicRoom() ? 1 : 0)); 480 pstmt.setInt(13, (room.isModerated() ? 1 : 0)); 481 pstmt.setInt(14, (room.isMembersOnly() ? 1 : 0)); 482 pstmt.setInt(15, (room.canOccupantsInvite() ? 1 : 0)); 483 pstmt.setString(16, room.getPassword()); 484 pstmt.setInt(17, (room.canAnyoneDiscoverJID() ? 1 : 0)); 485 pstmt.setInt(18, (room.isLogEnabled() ? 1 : 0)); 486 pstmt.setString(19, room.getSubject()); 487 pstmt.setInt(20, marshallRolesToBroadcast(room)); 488 pstmt.setInt(21, (room.isLoginRestrictedToNickname() ? 1 : 0)); 489 pstmt.setInt(22, (room.canChangeNickname() ? 1 : 0)); 490 pstmt.setInt(23, (room.isRegistrationEnabled() ? 1 : 0)); 491 switch (room.canSendPrivateMessage()) 492 { 493 default: 494 case "anyone": pstmt.setInt(24, 0); break; 495 case "participants": pstmt.setInt(24, 1); break; 496 case "moderators": pstmt.setInt(24, 2); break; 497 case "none": pstmt.setInt(24, 3); break; 498 } 499 pstmt.setInt(25, (room.isFmucEnabled() ? 1 : 0 )); 500 if ( room.getFmucOutboundNode() == null ) { 501 pstmt.setNull(26, Types.VARCHAR); 502 } else { 503 pstmt.setString(26, room.getFmucOutboundNode().toString()); 504 } 505 if ( room.getFmucOutboundMode() == null ) { 506 pstmt.setNull(27, Types.INTEGER); 507 } else { 508 pstmt.setInt(27, room.getFmucOutboundMode().equals(MasterMaster) ? 0 : 1); 509 } 510 511 // Store a newline-separated collection, which is an 'allow only on list' configuration. Note that the list can be empty (effectively: disallow all), or null: this is an 'allow all' configuration. 512 if (room.getFmucInboundNodes() == null) { 513 pstmt.setNull(28, Types.VARCHAR); // Null: allow all. 514 } else { 515 final String content = room.getFmucInboundNodes().stream().map(JID::toString).collect(Collectors.joining("\n")); // result potentially is an empty String, but will not be null. 516 pstmt.setString(28, content); 517 } 518 pstmt.executeUpdate(); 519 } 520 } 521 catch (SQLException sqle) { 522 Log.error(sqle.getMessage(), sqle); 523 } 524 finally { 525 DbConnectionManager.closeConnection(pstmt, con); 526 } 527 } 528 529 /** 530 * Removes the room configuration and its affiliates from the database. 531 * 532 * @param room the room to remove from the database. 533 */ deleteFromDB(MUCRoom room)534 public static void deleteFromDB(MUCRoom room) { 535 Log.debug("Attempting to delete room '{}' from the database.", room.getName()); 536 537 if (!room.isPersistent() || !room.wasSavedToDB()) { 538 return; 539 } 540 Connection con = null; 541 PreparedStatement pstmt = null; 542 boolean abortTransaction = false; 543 try { 544 con = DbConnectionManager.getTransactionConnection(); 545 pstmt = con.prepareStatement(DELETE_AFFILIATIONS); 546 pstmt.setLong(1, room.getID()); 547 pstmt.executeUpdate(); 548 DbConnectionManager.fastcloseStmt(pstmt); 549 550 pstmt = con.prepareStatement(DELETE_MEMBERS); 551 pstmt.setLong(1, room.getID()); 552 pstmt.executeUpdate(); 553 DbConnectionManager.fastcloseStmt(pstmt); 554 555 pstmt = con.prepareStatement(DELETE_ROOM); 556 pstmt.setLong(1, room.getID()); 557 pstmt.executeUpdate(); 558 559 // Update the room (in memory) to indicate the it's no longer in the database. 560 room.setSavedToDB(false); 561 } 562 catch (SQLException sqle) { 563 Log.error(sqle.getMessage(), sqle); 564 abortTransaction = true; 565 } 566 finally { 567 DbConnectionManager.closeStatement(pstmt); 568 DbConnectionManager.closeTransactionConnection(con, abortTransaction); 569 } 570 } 571 572 /** 573 * Loads the name of all the rooms that are in the database. 574 * 575 * @param chatserver the chat server that will hold the loaded rooms. 576 * @return a collection with all room names. 577 */ loadRoomNamesFromDB(MultiUserChatService chatserver)578 public static Collection<String> loadRoomNamesFromDB(MultiUserChatService chatserver) { 579 Log.debug("Loading room names for chat service {}", chatserver.getServiceName()); 580 Long serviceID = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatServiceID(chatserver.getServiceName()); 581 582 final Set<String> names = new HashSet<>(); 583 try { 584 Connection connection = null; 585 PreparedStatement statement = null; 586 ResultSet resultSet = null; 587 try { 588 connection = DbConnectionManager.getConnection(); 589 statement = connection.prepareStatement(LOAD_ALL_ROOM_NAMES); 590 statement.setLong(1, serviceID); 591 resultSet = statement.executeQuery(); 592 593 while (resultSet.next()) { 594 try { 595 names.add(resultSet.getString("name")); 596 } catch (SQLException e) { 597 Log.error("A database exception prevented one particular MUC room name to be loaded from the database.", e); 598 } 599 } 600 } finally { 601 DbConnectionManager.closeConnection(resultSet, statement, connection); 602 } 603 } 604 catch (SQLException sqle) { 605 Log.error("A database error prevented MUC room names to be loaded from the database.", sqle); 606 return Collections.emptyList(); 607 } 608 609 Log.debug( "Loaded {} room names for chat service {}", names.size(), chatserver.getServiceName() ); 610 return names; 611 } 612 613 /** 614 * Loads all the rooms that had occupants after a given date from the database. This query 615 * will be executed only when the service is starting up. 616 * 617 * @param chatserver the chat server that will hold the loaded rooms. 618 * @param cleanupDate rooms that hadn't been used after this date won't be loaded. 619 * @return a collection with all the persistent rooms. 620 */ loadRoomsFromDB(MultiUserChatService chatserver, Date cleanupDate)621 public static Collection<MUCRoom> loadRoomsFromDB(MultiUserChatService chatserver, Date cleanupDate) { 622 Log.debug( "Loading rooms for chat service {}", chatserver.getServiceName() ); 623 Long serviceID = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatServiceID(chatserver.getServiceName()); 624 625 final Map<Long, MUCRoom> rooms; 626 try { 627 rooms = loadRooms(serviceID, cleanupDate, chatserver); 628 loadHistory(serviceID, rooms); 629 loadAffiliations(serviceID, rooms); 630 loadMembers(serviceID, rooms); 631 } 632 catch (SQLException sqle) { 633 Log.error("A database error prevented MUC rooms to be loaded from the database.", sqle); 634 return Collections.emptyList(); 635 } 636 637 // Set now that the room's configuration is updated in the database. Note: We need to 638 // set this now since otherwise the room's affiliations will be saved to the database 639 // "again" while adding them to the room! 640 for (final MUCRoom room : rooms.values()) { 641 room.setSavedToDB(true); 642 if (room.getEmptyDate() == null) { 643 // The service process was killed somehow while the room was being used. Since 644 // the room won't have occupants at this time we need to set the best date when 645 // the last occupant left the room that we can 646 room.setEmptyDate(new Date()); 647 } 648 } 649 Log.debug( "Loaded {} rooms for chat service {}", rooms.size(), chatserver.getServiceName() ); 650 return rooms.values(); 651 } 652 loadRooms(Long serviceID, Date cleanupDate, MultiUserChatService chatserver)653 private static Map<Long, MUCRoom> loadRooms(Long serviceID, Date cleanupDate, MultiUserChatService chatserver) throws SQLException { 654 final Map<Long, MUCRoom> rooms = new HashMap<>(); 655 656 Connection connection = null; 657 PreparedStatement statement = null; 658 ResultSet resultSet = null; 659 try { 660 connection = DbConnectionManager.getConnection(); 661 if (cleanupDate!=null) 662 { 663 statement = connection.prepareStatement(RELOAD_ALL_ROOMS_WITH_RECENT_ACTIVITY); 664 statement.setLong(1, serviceID); 665 statement.setString(2, StringUtils.dateToMillis(cleanupDate)); 666 } 667 else 668 { 669 statement = connection.prepareStatement(LOAD_ALL_ROOMS); 670 statement.setLong(1, serviceID); 671 } 672 resultSet = statement.executeQuery(); 673 674 while (resultSet.next()) { 675 try { 676 MUCRoom room = new MUCRoom(chatserver, resultSet.getString("name")); 677 room.setID(resultSet.getLong("roomID")); 678 room.setCreationDate(new Date(Long.parseLong(resultSet.getString("creationDate").trim()))); 679 room.setModificationDate(new Date(Long.parseLong(resultSet.getString("modificationDate").trim()))); 680 room.setNaturalLanguageName(resultSet.getString("naturalName")); 681 room.setDescription(resultSet.getString("description")); 682 room.setLockedDate(new Date(Long.parseLong(resultSet.getString("lockedDate").trim()))); 683 if (resultSet.getString("emptyDate") != null) { 684 room.setEmptyDate(new Date(Long.parseLong(resultSet.getString("emptyDate").trim()))); 685 } 686 else { 687 room.setEmptyDate(null); 688 } 689 room.setCanOccupantsChangeSubject(resultSet.getInt("canChangeSubject") == 1); 690 room.setMaxUsers(resultSet.getInt("maxUsers")); 691 room.setPublicRoom(resultSet.getInt("publicRoom") == 1); 692 room.setModerated(resultSet.getInt("moderated") == 1); 693 room.setMembersOnly(resultSet.getInt("membersOnly") == 1); 694 room.setCanOccupantsInvite(resultSet.getInt("canInvite") == 1); 695 room.setPassword(resultSet.getString("roomPassword")); 696 room.setCanAnyoneDiscoverJID(resultSet.getInt("canDiscoverJID") == 1); 697 room.setLogEnabled(resultSet.getInt("logEnabled") == 1); 698 room.setSubject(resultSet.getString("subject")); 699 List<MUCRole.Role> rolesToBroadcast = new ArrayList<>(); 700 String roles = StringUtils.zeroPadString(Integer.toBinaryString(resultSet.getInt("rolesToBroadcast")), 3); 701 if (roles.charAt(0) == '1') { 702 rolesToBroadcast.add(MUCRole.Role.moderator); 703 } 704 if (roles.charAt(1) == '1') { 705 rolesToBroadcast.add(MUCRole.Role.participant); 706 } 707 if (roles.charAt(2) == '1') { 708 rolesToBroadcast.add(MUCRole.Role.visitor); 709 } 710 room.setRolesToBroadcastPresence(rolesToBroadcast); 711 room.setLoginRestrictedToNickname(resultSet.getInt("useReservedNick") == 1); 712 room.setChangeNickname(resultSet.getInt("canChangeNick") == 1); 713 room.setRegistrationEnabled(resultSet.getInt("canRegister") == 1); 714 switch (resultSet.getInt("allowpm")) // null returns 0. 715 { 716 default: 717 case 0: room.setCanSendPrivateMessage( "anyone" ); break; 718 case 1: room.setCanSendPrivateMessage( "participants" ); break; 719 case 2: room.setCanSendPrivateMessage( "moderators" ); break; 720 case 3: room.setCanSendPrivateMessage( "none" ); break; 721 } 722 723 room.setFmucEnabled(resultSet.getInt("fmucEnabled") == 1); 724 if ( resultSet.getString("fmucOutboundNode") != null ) { 725 final JID fmucOutboundNode = new JID(resultSet.getString("fmucOutboundNode")); 726 final FMUCMode fmucOutboundJoinMode; 727 switch (resultSet.getInt("fmucOutboundMode")) // null returns 0. 728 { 729 default: 730 case 0: fmucOutboundJoinMode = MasterMaster; break; 731 case 1: fmucOutboundJoinMode = FMUCMode.MasterSlave; break; 732 } 733 room.setFmucOutboundNode( fmucOutboundNode ); 734 room.setFmucOutboundMode( fmucOutboundJoinMode ); 735 } else { 736 room.setFmucOutboundNode( null ); 737 room.setFmucOutboundMode( null ); 738 } 739 if ( resultSet.getString("fmucInboundNodes") != null ) { 740 final Set<JID> fmucInboundNodes = Stream.of(resultSet.getString("fmucInboundNodes").split("\n")) 741 .map(String::trim) 742 .map(JID::new) 743 .collect(Collectors.toSet()); 744 // A list, which is an 'allow only on list' configuration. Note that the list can be empty (effectively: disallow all). 745 room.setFmucInboundNodes(fmucInboundNodes); 746 } else { 747 // Null: this is an 'allow all' configuration. 748 room.setFmucInboundNodes(null); 749 } 750 751 room.setPersistent(true); 752 rooms.put(room.getID(), room); 753 } catch (SQLException e) { 754 Log.error("A database exception prevented one particular MUC room to be loaded from the database.", e); 755 } 756 } 757 } finally { 758 DbConnectionManager.closeConnection(resultSet, statement, connection); 759 } 760 761 return rooms; 762 } 763 loadHistory(Long serviceID, Map<Long, MUCRoom> rooms)764 private static void loadHistory(Long serviceID, Map<Long, MUCRoom> rooms) throws SQLException { 765 Connection connection = null; 766 PreparedStatement statement = null; 767 ResultSet resultSet = null; 768 try { 769 connection = DbConnectionManager.getConnection(); 770 statement = connection.prepareStatement(LOAD_ALL_HISTORY); 771 772 // Reload the history, using "muc.history.reload.limit" (days) if present 773 long from = 0; 774 String reloadLimit = JiveGlobals.getProperty(MUC_HISTORY_RELOAD_LIMIT); 775 if (reloadLimit != null) { 776 // if the property is defined, but not numeric, default to 2 (days) 777 int reloadLimitDays = JiveGlobals.getIntProperty(MUC_HISTORY_RELOAD_LIMIT, 2); 778 Log.warn("MUC history reload limit set to " + reloadLimitDays + " days"); 779 from = System.currentTimeMillis() - (BigInteger.valueOf(86400000).multiply(BigInteger.valueOf(reloadLimitDays))).longValue(); 780 } 781 statement.setLong(1, serviceID); 782 statement.setString(2, StringUtils.dateToMillis(new Date(from))); 783 resultSet = statement.executeQuery(); 784 785 while (resultSet.next()) { 786 try { 787 MUCRoom room = rooms.get(resultSet.getLong("roomID")); 788 // Skip to the next position if the room does not exist or if history is disabled 789 if (room == null || !room.isLogEnabled()) { 790 continue; 791 } 792 String senderJID = resultSet.getString("sender"); 793 String nickname = resultSet.getString("nickname"); 794 Date sentDate = new Date(Long.parseLong(resultSet.getString("logTime").trim())); 795 String subject = resultSet.getString("subject"); 796 String body = resultSet.getString("body"); 797 String stanza = resultSet.getString("stanza"); 798 room.getRoomHistory().addOldMessage(senderJID, nickname, sentDate, subject, body, stanza); 799 } catch (SQLException e) { 800 Log.warn("A database exception prevented the history for one particular MUC room to be loaded from the database.", e); 801 } 802 } 803 } finally { 804 DbConnectionManager.closeConnection(resultSet, statement, connection); 805 } 806 807 // Add the last known room subject to the room history only for those rooms that still 808 // don't have in their histories the last room subject 809 for (MUCRoom loadedRoom : rooms.values()) 810 { 811 if (!loadedRoom.getRoomHistory().hasChangedSubject() 812 && loadedRoom.getSubject() != null 813 && loadedRoom.getSubject().length() > 0) 814 { 815 loadedRoom.getRoomHistory().addOldMessage( loadedRoom.getRole().getRoleAddress().toString(), 816 null, 817 loadedRoom.getModificationDate(), 818 loadedRoom.getSubject(), 819 null, 820 null); 821 } 822 } 823 } 824 loadAffiliations(Long serviceID, Map<Long, MUCRoom> rooms)825 private static void loadAffiliations(Long serviceID, Map<Long, MUCRoom> rooms) throws SQLException { 826 Connection connection = null; 827 PreparedStatement statement = null; 828 ResultSet resultSet = null; 829 try { 830 connection = DbConnectionManager.getConnection(); 831 statement = connection.prepareStatement(LOAD_ALL_AFFILIATIONS); 832 statement.setLong(1, serviceID); 833 resultSet = statement.executeQuery(); 834 835 while (resultSet.next()) { 836 try { 837 long roomID = resultSet.getLong("roomID"); 838 MUCRoom room = rooms.get(roomID); 839 // Skip to the next position if the room does not exist 840 if (room == null) { 841 continue; 842 } 843 844 final MUCRole.Affiliation affiliation = MUCRole.Affiliation.valueOf(resultSet.getInt("affiliation")); 845 846 final String jidValue = resultSet.getString("jid"); 847 final JID affiliationJID; 848 try { 849 // might be a group JID 850 affiliationJID = GroupJID.fromString(jidValue); 851 } catch (IllegalArgumentException ex) { 852 Log.warn("An illegal JID ({}) was found in the database, " 853 + "while trying to load all affiliations for room " 854 + "{}. The JID is ignored." 855 , new Object[] { jidValue, roomID }); 856 continue; 857 } 858 859 try { 860 switch (affiliation) { 861 case owner: 862 room.addOwner(affiliationJID, room.getRole()); 863 break; 864 case admin: 865 room.addAdmin(affiliationJID, room.getRole()); 866 break; 867 case outcast: 868 room.addOutcast(affiliationJID, null, room.getRole()); 869 break; 870 default: 871 Log.error("Unknown affiliation value " + affiliation + " for user " + affiliationJID + " in persistent room " + room.getID()); 872 } 873 } catch (ForbiddenException | ConflictException | NotAllowedException e) { 874 Log.warn("An exception prevented affiliations to be added to the room with id " + roomID, e); 875 } 876 } catch (SQLException e) { 877 Log.error("A database exception prevented affiliations for one particular MUC room to be loaded from the database.", e); 878 } 879 } 880 881 } finally { 882 DbConnectionManager.closeConnection(resultSet, statement, connection); 883 } 884 } 885 loadMembers(Long serviceID, Map<Long, MUCRoom> rooms)886 private static void loadMembers(Long serviceID, Map<Long, MUCRoom> rooms) throws SQLException { 887 Connection connection = null; 888 PreparedStatement statement = null; 889 ResultSet resultSet = null; 890 JID affiliationJID = null; 891 try { 892 connection = DbConnectionManager.getConnection(); 893 statement = connection.prepareStatement(LOAD_ALL_MEMBERS); 894 statement.setLong(1, serviceID); 895 resultSet = statement.executeQuery(); 896 897 while (resultSet.next()) { 898 try { 899 MUCRoom room = rooms.get(resultSet.getLong("roomID")); 900 // Skip to the next position if the room does not exist 901 if (room == null) { 902 continue; 903 } 904 try { 905 // might be a group JID 906 affiliationJID = GroupJID.fromString(resultSet.getString("jid")); 907 room.addMember(affiliationJID, resultSet.getString("nickname"), room.getRole()); 908 } catch (ForbiddenException | ConflictException e) { 909 Log.warn("Unable to add member to room.", e); 910 } 911 } catch (SQLException e) { 912 Log.error("A database exception prevented members for one particular MUC room to be loaded from the database.", e); 913 } 914 } 915 } finally { 916 DbConnectionManager.closeConnection(resultSet, statement, connection); 917 } 918 } 919 920 /** 921 * Updates the room's subject in the database. 922 * 923 * @param room the room to update its subject in the database. 924 */ updateRoomSubject(MUCRoom room)925 public static void updateRoomSubject(MUCRoom room) { 926 if (!room.isPersistent() || !room.wasSavedToDB()) { 927 return; 928 } 929 930 Connection con = null; 931 PreparedStatement pstmt = null; 932 try { 933 con = DbConnectionManager.getConnection(); 934 pstmt = con.prepareStatement(UPDATE_SUBJECT); 935 pstmt.setString(1, room.getSubject()); 936 pstmt.setLong(2, room.getID()); 937 pstmt.executeUpdate(); 938 } 939 catch (SQLException sqle) { 940 Log.error(sqle.getMessage(), sqle); 941 } 942 finally { 943 DbConnectionManager.closeConnection(pstmt, con); 944 } 945 } 946 947 /** 948 * Updates the room's lock status in the database. 949 * 950 * @param room the room to update its lock status in the database. 951 */ updateRoomLock(MUCRoom room)952 public static void updateRoomLock(MUCRoom room) { 953 if (!room.isPersistent() || !room.wasSavedToDB()) { 954 return; 955 } 956 957 Connection con = null; 958 PreparedStatement pstmt = null; 959 try { 960 con = DbConnectionManager.getConnection(); 961 pstmt = con.prepareStatement(UPDATE_LOCK); 962 pstmt.setString(1, StringUtils.dateToMillis(room.getLockedDate())); 963 pstmt.setLong(2, room.getID()); 964 pstmt.executeUpdate(); 965 } 966 catch (SQLException sqle) { 967 Log.error(sqle.getMessage(), sqle); 968 } 969 finally { 970 DbConnectionManager.closeConnection(pstmt, con); 971 } 972 } 973 974 /** 975 * Updates the room's lock status in the database. 976 * 977 * @param room the room to update its lock status in the database. 978 */ updateRoomEmptyDate(MUCRoom room)979 public static void updateRoomEmptyDate(MUCRoom room) { 980 if (!room.isPersistent() || !room.wasSavedToDB()) { 981 return; 982 } 983 984 Connection con = null; 985 PreparedStatement pstmt = null; 986 try { 987 con = DbConnectionManager.getConnection(); 988 pstmt = con.prepareStatement(UPDATE_EMPTYDATE); 989 Date emptyDate = room.getEmptyDate(); 990 if (emptyDate == null) { 991 pstmt.setString(1, null); 992 } 993 else { 994 pstmt.setString(1, StringUtils.dateToMillis(emptyDate)); 995 } 996 pstmt.setLong(2, room.getID()); 997 pstmt.executeUpdate(); 998 } 999 catch (SQLException sqle) { 1000 Log.error(sqle.getMessage(), sqle); 1001 } 1002 finally { 1003 DbConnectionManager.closeConnection(pstmt, con); 1004 } 1005 } 1006 1007 /** 1008 * Update the DB with the new affiliation of the user in the room. The new information will be 1009 * saved only if the room is_persistent and has already been saved to the database previously. 1010 * 1011 * @param room The room where the affiliation of the user was updated. 1012 * @param jid The bareJID of the user to update this affiliation. 1013 * @param nickname The reserved nickname of the user in the room or null if none. 1014 * @param newAffiliation the new affiliation of the user in the room. 1015 * @param oldAffiliation the previous affiliation of the user in the room. 1016 */ saveAffiliationToDB(MUCRoom room, JID jid, String nickname, MUCRole.Affiliation newAffiliation, MUCRole.Affiliation oldAffiliation)1017 public static void saveAffiliationToDB(MUCRoom room, JID jid, String nickname, 1018 MUCRole.Affiliation newAffiliation, MUCRole.Affiliation oldAffiliation) 1019 { 1020 final String affiliationJid = jid.toBareJID(); 1021 if (!room.isPersistent() || !room.wasSavedToDB()) { 1022 return; 1023 } 1024 if (MUCRole.Affiliation.none == oldAffiliation) { 1025 if (MUCRole.Affiliation.member == newAffiliation) { 1026 // Add the user to the members table 1027 Connection con = null; 1028 PreparedStatement pstmt = null; 1029 try { 1030 con = DbConnectionManager.getConnection(); 1031 pstmt = con.prepareStatement(ADD_MEMBER); 1032 pstmt.setLong(1, room.getID()); 1033 pstmt.setString(2, affiliationJid); 1034 pstmt.setString(3, nickname); 1035 pstmt.executeUpdate(); 1036 } 1037 catch (SQLException sqle) { 1038 Log.error(sqle.getMessage(), sqle); 1039 } 1040 finally { 1041 DbConnectionManager.closeConnection(pstmt, con); 1042 } 1043 } 1044 else { 1045 // Add the user to the generic affiliations table 1046 Connection con = null; 1047 PreparedStatement pstmt = null; 1048 try { 1049 con = DbConnectionManager.getConnection(); 1050 pstmt = con.prepareStatement(ADD_AFFILIATION); 1051 pstmt.setLong(1, room.getID()); 1052 pstmt.setString(2, affiliationJid); 1053 pstmt.setInt(3, newAffiliation.getValue()); 1054 pstmt.executeUpdate(); 1055 } 1056 catch (SQLException sqle) { 1057 Log.error(sqle.getMessage(), sqle); 1058 } 1059 finally { 1060 DbConnectionManager.closeConnection(pstmt, con); 1061 } 1062 } 1063 } 1064 else { 1065 if (MUCRole.Affiliation.member == newAffiliation && 1066 MUCRole.Affiliation.member == oldAffiliation) 1067 { 1068 // Update the member's data in the member table. 1069 Connection con = null; 1070 PreparedStatement pstmt = null; 1071 try { 1072 con = DbConnectionManager.getConnection(); 1073 pstmt = con.prepareStatement(UPDATE_MEMBER); 1074 pstmt.setString(1, nickname); 1075 pstmt.setLong(2, room.getID()); 1076 pstmt.setString(3, affiliationJid); 1077 pstmt.executeUpdate(); 1078 } 1079 catch (SQLException sqle) { 1080 Log.error(sqle.getMessage(), sqle); 1081 } 1082 finally { 1083 DbConnectionManager.closeConnection(pstmt, con); 1084 } 1085 } 1086 else if (MUCRole.Affiliation.member == newAffiliation) { 1087 Connection con = null; 1088 PreparedStatement pstmt = null; 1089 boolean abortTransaction = false; 1090 try { 1091 // Remove the user from the generic affiliations table 1092 con = DbConnectionManager.getTransactionConnection(); 1093 pstmt = con.prepareStatement(DELETE_AFFILIATION); 1094 pstmt.setLong(1, room.getID()); 1095 pstmt.setString(2, affiliationJid); 1096 pstmt.executeUpdate(); 1097 DbConnectionManager.fastcloseStmt(pstmt); 1098 1099 // Add them as a member. 1100 pstmt = con.prepareStatement(ADD_MEMBER); 1101 pstmt.setLong(1, room.getID()); 1102 pstmt.setString(2, affiliationJid); 1103 pstmt.setString(3, nickname); 1104 pstmt.executeUpdate(); 1105 } 1106 catch (SQLException sqle) { 1107 Log.error(sqle.getMessage(), sqle); 1108 abortTransaction = true; 1109 } 1110 finally { 1111 DbConnectionManager.closeStatement(pstmt); 1112 DbConnectionManager.closeTransactionConnection(con, abortTransaction); 1113 } 1114 } 1115 else if (MUCRole.Affiliation.member == oldAffiliation) { 1116 Connection con = null; 1117 PreparedStatement pstmt = null; 1118 boolean abortTransaction = false; 1119 try { 1120 con = DbConnectionManager.getTransactionConnection(); 1121 pstmt = con.prepareStatement(DELETE_MEMBER); 1122 pstmt.setLong(1, room.getID()); 1123 pstmt.setString(2, affiliationJid); 1124 pstmt.executeUpdate(); 1125 DbConnectionManager.fastcloseStmt(pstmt); 1126 1127 pstmt = con.prepareStatement(ADD_AFFILIATION); 1128 pstmt.setLong(1, room.getID()); 1129 pstmt.setString(2, affiliationJid); 1130 pstmt.setInt(3, newAffiliation.getValue()); 1131 pstmt.executeUpdate(); 1132 } 1133 catch (SQLException sqle) { 1134 Log.error(sqle.getMessage(), sqle); 1135 abortTransaction = true; 1136 } 1137 finally { 1138 DbConnectionManager.closeStatement(pstmt); 1139 DbConnectionManager.closeTransactionConnection(con, abortTransaction); 1140 } 1141 } 1142 else { 1143 // Update the user in the generic affiliations table. 1144 Connection con = null; 1145 PreparedStatement pstmt = null; 1146 try { 1147 con = DbConnectionManager.getConnection(); 1148 pstmt = con.prepareStatement(UPDATE_AFFILIATION); 1149 pstmt.setInt(1, newAffiliation.getValue()); 1150 pstmt.setLong(2, room.getID()); 1151 pstmt.setString(3, affiliationJid); 1152 pstmt.executeUpdate(); 1153 } 1154 catch (SQLException sqle) { 1155 Log.error(sqle.getMessage(), sqle); 1156 } 1157 finally { 1158 DbConnectionManager.closeConnection(pstmt, con); 1159 } 1160 } 1161 } 1162 } 1163 1164 /** 1165 * Removes the affiliation of the user from the DB if the room is persistent. 1166 * 1167 * @param room The room where the affiliation of the user was removed. 1168 * @param jid The bareJID of the user to remove his affiliation. 1169 * @param oldAffiliation the previous affiliation of the user in the room. 1170 */ removeAffiliationFromDB(MUCRoom room, JID jid, MUCRole.Affiliation oldAffiliation)1171 public static void removeAffiliationFromDB(MUCRoom room, JID jid, 1172 MUCRole.Affiliation oldAffiliation) 1173 { 1174 final String affiliationJID = jid.toBareJID(); 1175 if (room.isPersistent() && room.wasSavedToDB()) { 1176 if (MUCRole.Affiliation.member == oldAffiliation) { 1177 // Remove the user from the members table 1178 Connection con = null; 1179 PreparedStatement pstmt = null; 1180 try { 1181 con = DbConnectionManager.getConnection(); 1182 pstmt = con.prepareStatement(DELETE_MEMBER); 1183 pstmt.setLong(1, room.getID()); 1184 pstmt.setString(2, affiliationJID); 1185 pstmt.executeUpdate(); 1186 } 1187 catch (SQLException sqle) { 1188 Log.error(sqle.getMessage(), sqle); 1189 } 1190 finally { 1191 DbConnectionManager.closeConnection(pstmt, con); 1192 } 1193 } 1194 else { 1195 // Remove the user from the generic affiliations table 1196 Connection con = null; 1197 PreparedStatement pstmt = null; 1198 try { 1199 con = DbConnectionManager.getConnection(); 1200 pstmt = con.prepareStatement(DELETE_AFFILIATION); 1201 pstmt.setLong(1, room.getID()); 1202 pstmt.setString(2, affiliationJID); 1203 pstmt.executeUpdate(); 1204 } 1205 catch (SQLException sqle) { 1206 Log.error(sqle.getMessage(), sqle); 1207 } 1208 finally { 1209 DbConnectionManager.closeConnection(pstmt, con); 1210 } 1211 } 1212 } 1213 } 1214 1215 /** 1216 * Removes the affiliation of the user from the DB if ANY room that is persistent. 1217 * 1218 * @param affiliationJID The bareJID of the user to remove his affiliation from ALL persistent rooms. 1219 */ removeAffiliationFromDB(JID affiliationJID)1220 public static void removeAffiliationFromDB(JID affiliationJID) 1221 { 1222 Connection con = null; 1223 PreparedStatement pstmt = null; 1224 try { 1225 con = DbConnectionManager.getConnection(); 1226 // Remove the user from the members table 1227 pstmt = con.prepareStatement(DELETE_USER_MEMBER); 1228 pstmt.setString(1, affiliationJID.toBareJID()); 1229 pstmt.executeUpdate(); 1230 DbConnectionManager.fastcloseStmt(pstmt); 1231 1232 // Remove the user from the generic affiliations table 1233 pstmt = con.prepareStatement(DELETE_USER_MUCAFFILIATION); 1234 pstmt.setString(1, affiliationJID.toBareJID()); 1235 pstmt.executeUpdate(); 1236 } 1237 catch (SQLException sqle) { 1238 Log.error(sqle.getMessage(), sqle); 1239 } 1240 finally { 1241 DbConnectionManager.closeConnection(pstmt, con); 1242 } 1243 } 1244 1245 /** 1246 * Saves the conversation log entry batch to the database. 1247 * 1248 * @param batch a list of ConversationLogEntry to save to the database. 1249 * @return true if the batch was saved successfully to the database. 1250 */ saveConversationLogBatch(List<ConversationLogEntry> batch)1251 public static boolean saveConversationLogBatch(List<ConversationLogEntry> batch) { 1252 Connection con = null; 1253 PreparedStatement pstmt = null; 1254 1255 try { 1256 con = DbConnectionManager.getConnection(); 1257 pstmt = con.prepareStatement(ADD_CONVERSATION_LOG); 1258 con.setAutoCommit(false); 1259 1260 for(ConversationLogEntry entry : batch) { 1261 pstmt.setLong(1, entry.getRoomID()); 1262 pstmt.setLong(2, entry.getMessageID()); 1263 pstmt.setString(3, entry.getSender().toString()); 1264 pstmt.setString(4, entry.getNickname()); 1265 pstmt.setString(5, StringUtils.dateToMillis(entry.getDate())); 1266 pstmt.setString(6, entry.getSubject()); 1267 pstmt.setString(7, entry.getBody()); 1268 pstmt.setString(8, entry.getStanza()); 1269 pstmt.addBatch(); 1270 } 1271 1272 pstmt.executeBatch(); 1273 con.commit(); 1274 return true; 1275 } 1276 catch (SQLException sqle) { 1277 Log.error("Error saving conversation log batch", sqle); 1278 if (con != null) { 1279 try { 1280 con.rollback(); 1281 } catch (SQLException ignore) {} 1282 } 1283 return false; 1284 } 1285 finally { 1286 DbConnectionManager.closeConnection(pstmt, con); 1287 } 1288 } 1289 1290 /** 1291 * Returns an integer based on the binary representation of the roles to broadcast. 1292 * 1293 * @param room the room to marshall its roles to broadcast. 1294 * @return an integer based on the binary representation of the roles to broadcast. 1295 */ marshallRolesToBroadcast(MUCRoom room)1296 private static int marshallRolesToBroadcast(MUCRoom room) { 1297 final String buffer = 1298 (room.canBroadcastPresence(MUCRole.Role.moderator) ? "1" : "0") + 1299 (room.canBroadcastPresence(MUCRole.Role.participant) ? "1" : "0") + 1300 (room.canBroadcastPresence(MUCRole.Role.visitor) ? "1" : "0"); 1301 return Integer.parseInt(buffer, 2); 1302 } 1303 1304 /** 1305 * Returns a Jive property. 1306 * 1307 * @param subdomain the subdomain of the service to retrieve a property from 1308 * @param name the name of the property to return. 1309 * @return the property value specified by name. 1310 */ getProperty(String subdomain, String name)1311 public static String getProperty(String subdomain, String name) { 1312 final MUCServiceProperties props = propertyMaps.computeIfAbsent( subdomain, MUCServiceProperties::new ); 1313 return props.get(name); 1314 } 1315 1316 /** 1317 * Returns a Jive property. If the specified property doesn't exist, the 1318 * {@code defaultValue} will be returned. 1319 * 1320 * @param subdomain the subdomain of the service to retrieve a property from 1321 * @param name the name of the property to return. 1322 * @param defaultValue value returned if the property doesn't exist. 1323 * @return the property value specified by name. 1324 */ getProperty(String subdomain, String name, String defaultValue)1325 public static String getProperty(String subdomain, String name, String defaultValue) { 1326 final String value = getProperty(subdomain, name); 1327 if (value != null) { 1328 return value; 1329 } else { 1330 return defaultValue; 1331 } 1332 } 1333 1334 /** 1335 * Returns an integer value Jive property. If the specified property doesn't exist, the 1336 * {@code defaultValue} will be returned. 1337 * 1338 * @param subdomain the subdomain of the service to retrieve a property from 1339 * @param name the name of the property to return. 1340 * @param defaultValue value returned if the property doesn't exist or was not 1341 * a number. 1342 * @return the property value specified by name or {@code defaultValue}. 1343 */ getIntProperty(String subdomain, String name, int defaultValue)1344 public static int getIntProperty(String subdomain, String name, int defaultValue) { 1345 String value = getProperty(subdomain, name); 1346 if (value != null) { 1347 try { 1348 return Integer.parseInt(value); 1349 } 1350 catch (NumberFormatException nfe) { 1351 // Ignore. 1352 } 1353 } 1354 return defaultValue; 1355 } 1356 1357 /** 1358 * Returns a long value Jive property. If the specified property doesn't exist, the 1359 * {@code defaultValue} will be returned. 1360 * 1361 * @param subdomain the subdomain of the service to retrieve a property from 1362 * @param name the name of the property to return. 1363 * @param defaultValue value returned if the property doesn't exist or was not 1364 * a number. 1365 * @return the property value specified by name or {@code defaultValue}. 1366 */ getLongProperty(String subdomain, String name, long defaultValue)1367 public static long getLongProperty(String subdomain, String name, long defaultValue) { 1368 String value = getProperty(subdomain, name); 1369 if (value != null) { 1370 try { 1371 return Long.parseLong(value); 1372 } 1373 catch (NumberFormatException nfe) { 1374 // Ignore. 1375 } 1376 } 1377 return defaultValue; 1378 } 1379 1380 /** 1381 * Returns a boolean value Jive property. 1382 * 1383 * @param subdomain the subdomain of the service to retrieve a property from 1384 * @param name the name of the property to return. 1385 * @return true if the property value exists and is set to {@code "true"} (ignoring case). 1386 * Otherwise {@code false} is returned. 1387 */ getBooleanProperty(String subdomain, String name)1388 public static boolean getBooleanProperty(String subdomain, String name) { 1389 return Boolean.valueOf(getProperty(subdomain, name)); 1390 } 1391 1392 /** 1393 * Returns a boolean value Jive property. If the property doesn't exist, the {@code defaultValue} 1394 * will be returned. 1395 * 1396 * If the specified property can't be found, or if the value is not a number, the 1397 * {@code defaultValue} will be returned. 1398 * 1399 * @param subdomain the subdomain of the service to retrieve a property from 1400 * @param name the name of the property to return. 1401 * @param defaultValue value returned if the property doesn't exist. 1402 * @return true if the property value exists and is set to {@code "true"} (ignoring case). 1403 * Otherwise {@code false} is returned. 1404 */ getBooleanProperty(String subdomain, String name, boolean defaultValue)1405 public static boolean getBooleanProperty(String subdomain, String name, boolean defaultValue) { 1406 String value = getProperty(subdomain, name); 1407 if (value != null) { 1408 return Boolean.valueOf(value); 1409 } 1410 else { 1411 return defaultValue; 1412 } 1413 } 1414 1415 /** 1416 * Return all immediate children property names of a parent Jive property as a list of strings, 1417 * or an empty list if there are no children. For example, given 1418 * the properties {@code X.Y.A}, {@code X.Y.B}, {@code X.Y.C} and {@code X.Y.C.D}, then 1419 * the immediate child properties of {@code X.Y} are {@code A}, {@code B}, and 1420 * {@code C} ({@code C.D} would not be returned using this method).<p> 1421 * 1422 * @param subdomain the subdomain of the service to retrieve a property from 1423 * @param parent the root "node" of the properties to retrieve 1424 * @return a List of all immediate children property names (Strings). 1425 */ getPropertyNames(String subdomain, String parent)1426 public static List<String> getPropertyNames(String subdomain, String parent) { 1427 final MUCServiceProperties props = propertyMaps.computeIfAbsent( subdomain, MUCServiceProperties::new ); 1428 return new ArrayList<>(props.getChildrenNames(parent)); 1429 } 1430 1431 /** 1432 * Return all immediate children property values of a parent Jive property as a list of strings, 1433 * or an empty list if there are no children. For example, given 1434 * the properties {@code X.Y.A}, {@code X.Y.B}, {@code X.Y.C} and {@code X.Y.C.D}, then 1435 * the immediate child properties of {@code X.Y} are {@code X.Y.A}, {@code X.Y.B}, and 1436 * {@code X.Y.C} (the value of {@code X.Y.C.D} would not be returned using this method).<p> 1437 * 1438 * @param subdomain the subdomain of the service to retrieve a property from 1439 * @param parent the name of the parent property to return the children for. 1440 * @return all child property values for the given parent. 1441 */ getProperties(String subdomain, String parent)1442 public static List<String> getProperties(String subdomain, String parent) { 1443 final MUCServiceProperties props = propertyMaps.computeIfAbsent( subdomain, MUCServiceProperties::new ); 1444 Collection<String> propertyNames = props.getChildrenNames(parent); 1445 List<String> values = new ArrayList<>(); 1446 for (String propertyName : propertyNames) { 1447 String value = getProperty(subdomain, propertyName); 1448 if (value != null) { 1449 values.add(value); 1450 } 1451 } 1452 1453 return values; 1454 } 1455 1456 /** 1457 * Returns all MUC service property names. 1458 * 1459 * @param subdomain the subdomain of the service to retrieve a property from 1460 * @return a List of all property names (Strings). 1461 */ getPropertyNames(String subdomain)1462 public static List<String> getPropertyNames(String subdomain) { 1463 final MUCServiceProperties props = propertyMaps.computeIfAbsent( subdomain, MUCServiceProperties::new ); 1464 return new ArrayList<>(props.getPropertyNames()); 1465 } 1466 1467 /** 1468 * Sets a Jive property. If the property doesn't already exists, a new 1469 * one will be created. 1470 * 1471 * @param subdomain the subdomain of the service to set a property for 1472 * @param name the name of the property being set. 1473 * @param value the value of the property being set. 1474 */ setProperty(String subdomain, String name, String value)1475 public static void setProperty(String subdomain, String name, String value) { 1476 MUCServiceProperties properties = propertyMaps.get(subdomain); 1477 if (properties == null) { 1478 properties = new MUCServiceProperties(subdomain); 1479 } 1480 properties.put(name, value); 1481 propertyMaps.put(subdomain, properties); 1482 } 1483 setLocalProperty(String subdomain, String name, String value)1484 public static void setLocalProperty(String subdomain, String name, String value) { 1485 MUCServiceProperties properties = propertyMaps.get(subdomain); 1486 if (properties == null) { 1487 properties = new MUCServiceProperties(subdomain); 1488 } 1489 properties.localPut(name, value); 1490 propertyMaps.put(subdomain, properties); 1491 } 1492 1493 /** 1494 * Sets multiple Jive properties at once. If a property doesn't already exists, a new 1495 * one will be created. 1496 * 1497 * @param subdomain the subdomain of the service to set properties for 1498 * @param propertyMap a map of properties, keyed on property name. 1499 */ setProperties(String subdomain, Map<String, String> propertyMap)1500 public static void setProperties(String subdomain, Map<String, String> propertyMap) { 1501 MUCServiceProperties properties = propertyMaps.get(subdomain); 1502 if (properties == null) { 1503 properties = new MUCServiceProperties(subdomain); 1504 } 1505 properties.putAll(propertyMap); 1506 propertyMaps.put(subdomain, properties); 1507 } 1508 1509 /** 1510 * Deletes a Jive property. If the property doesn't exist, the method 1511 * does nothing. All children of the property will be deleted as well. 1512 * 1513 * @param subdomain the subdomain of the service to delete a property from 1514 * @param name the name of the property to delete. 1515 */ deleteProperty(String subdomain, String name)1516 public static void deleteProperty(String subdomain, String name) { 1517 MUCServiceProperties properties = propertyMaps.get(subdomain); 1518 if (properties == null) { 1519 properties = new MUCServiceProperties(subdomain); 1520 } 1521 properties.remove(name); 1522 propertyMaps.put(subdomain, properties); 1523 } 1524 deleteLocalProperty(String subdomain, String name)1525 public static void deleteLocalProperty(String subdomain, String name) { 1526 MUCServiceProperties properties = propertyMaps.get(subdomain); 1527 if (properties == null) { 1528 properties = new MUCServiceProperties(subdomain); 1529 } 1530 properties.localRemove(name); 1531 propertyMaps.put(subdomain, properties); 1532 } 1533 1534 /** 1535 * Resets (reloads) the properties for a specified subdomain. 1536 * 1537 * @param subdomain the subdomain of the service to reload properties for. 1538 */ refreshProperties(String subdomain)1539 public static void refreshProperties(String subdomain) { 1540 propertyMaps.replace(subdomain, new MUCServiceProperties(subdomain)); 1541 } 1542 1543 } 1544