Commit 584973dd authored by Leon Roy's avatar Leon Roy Committed by leonroy

OF-664 - Monitoring archive shows null in room chat logs

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@13627 b35dd754-fafc-0310-a699-88a17e54d16e
parent 7668d387
......@@ -50,594 +50,578 @@ import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.util.cache.ExternalizableUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/**
* Represents an IM conversation between two people. A conversation encompasses a
* series of messages sent back and forth. It may cover a single topic or several.
* The start of a conversation occurs when the first message between two users is
* sent. It ends when either:
* Represents an IM conversation between two people. A conversation encompasses a series of messages sent back and forth. It may cover a single topic
* or several. The start of a conversation occurs when the first message between two users is sent. It ends when either:
* <ul>
* <li>No messages are sent between the users for a certain period of time (default of 10
* minutes). The default value can be overridden by setting the Openfire property
* <tt>conversation.idleTime</tt>.</li>
* <li>The total conversation time reaches a maximum value (default of 60 minutes).
* The default value can be overridden by setting the Openfire property
* <tt>conversation.maxTime</tt>. When the max time has been reached and additional
* messages are sent between the users, a new conversation will simply be
* started.</li>
* <li>No messages are sent between the users for a certain period of time (default of 10 minutes). The default value can be overridden by setting the
* Openfire property <tt>conversation.idleTime</tt>.</li>
* <li>The total conversation time reaches a maximum value (default of 60 minutes). The default value can be overridden by setting the Openfire
* property <tt>conversation.maxTime</tt>. When the max time has been reached and additional messages are sent between the users, a new conversation
* will simply be started.</li>
* </ul>
* <p/>
* Each conversation has a start time, date of the last message, and count of the
* messages in the conversation. Conversations are specially marked if one of the
* participants is on an external server. If archiving is enabled, the actual messages in
* the conversation can be retrieved.
*
* Each conversation has a start time, date of the last message, and count of the messages in the conversation. Conversations are specially marked if
* one of the participants is on an external server. If archiving is enabled, the actual messages in the conversation can be retrieved.
*
* @author Matt Tucker
*/
@JiveID(50)
public class Conversation implements Externalizable {
private static final Logger Log = LoggerFactory.getLogger(Conversation.class);
private static final String INSERT_CONVERSATION =
"INSERT INTO ofConversation(conversationID, room, isExternal, startDate, " +
"lastActivity, messageCount) VALUES (?,?,?,?,?,0)";
private static final String INSERT_PARTICIPANT =
"INSERT INTO ofConParticipant(conversationID, joinedDate, bareJID, jidResource, nickname) " +
"VALUES (?,?,?,?,?)";
private static final String LOAD_CONVERSATION =
"SELECT room, isExternal, startDate, lastActivity, messageCount " +
"FROM ofConversation WHERE conversationID=?";
private static final String LOAD_PARTICIPANTS =
"SELECT bareJID, jidResource, nickname, joinedDate, leftDate FROM ofConParticipant " +
"WHERE conversationID=? ORDER BY joinedDate";
private static final String LOAD_MESSAGES =
"SELECT fromJID, toJID, sentDate, body FROM ofMessageArchive WHERE conversationID=? " +
"ORDER BY sentDate";
private transient ConversationManager conversationManager;
private long conversationID = -1;
private Map<String, UserParticipations> participants;
private boolean external;
private Date startDate;
private Date lastActivity;
private int messageCount;
/**
* Room where the group conversion is taking place. For one-to-one chats
* there is no room so this variable will be null.
*/
private JID room;
/**
* Do not use this constructor. It only exists for serialization purposes.
*/
public Conversation() {
}
/**
* Constructs a new one-to-one conversation.
*
* @param conversationManager the ConversationManager.
* @param users the two participants in the conversation.
* @param external true if the conversation includes a user on another server.
* @param startDate the starting date of the conversation.
*/
public Conversation(ConversationManager conversationManager, Collection<JID> users,
boolean external, Date startDate) {
if (users.size() != 2) {
throw new IllegalArgumentException("Illegal number of participants: " + users.size());
}
this.conversationManager = conversationManager;
this.participants = new HashMap<String, UserParticipations>(2);
// Ensure that we're use the full JID of each participant.
for (JID user : users) {
UserParticipations userParticipations = new UserParticipations(false);
userParticipations.addParticipation(new ConversationParticipation(startDate));
participants.put(user.toString(), userParticipations);
}
this.external = external;
this.startDate = startDate;
this.lastActivity = startDate;
// If archiving is enabled, insert the conversation into the database.
if (conversationManager.isMetadataArchivingEnabled()) {
try {
insertIntoDb();
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
}
/**
* Constructs a new group chat conversation that is taking place in a room.
*
* @param conversationManager the ConversationManager.
* @param room the JID of the room where the conversation is taking place.
* @param external true if the conversation includes a user on another server.
* @param startDate the starting date of the conversation.
*/
public Conversation(ConversationManager conversationManager, JID room, boolean external, Date startDate) {
this.conversationManager = conversationManager;
this.participants = new ConcurrentHashMap<String, UserParticipations>();
// Add list of existing room occupants as participants of this conversation
MUCRoom mucRoom = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(room).getChatRoom(room.getNode());
if (mucRoom != null) {
for (MUCRole role : mucRoom.getOccupants()) {
UserParticipations userParticipations = new UserParticipations(true);
userParticipations.addParticipation(new ConversationParticipation(startDate, role.getNickname()));
participants.put(role.getUserAddress().toString(), userParticipations);
}
}
this.room = room;
this.external = external;
this.startDate = startDate;
this.lastActivity = startDate;
// If archiving is enabled, insert the conversation into the database.
if (conversationManager.isMetadataArchivingEnabled()) {
try {
insertIntoDb();
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
}
/**
* Loads a conversation from the database.
*
* @param conversationManager the conversation manager.
* @param conversationID the ID of the conversation.
* @throws NotFoundException if the conversation can't be loaded.
*/
public Conversation(ConversationManager conversationManager, long conversationID)
throws NotFoundException {
this.conversationManager = conversationManager;
this.conversationID = conversationID;
loadFromDb();
}
/**
* Returns the unique ID of the conversation. A unique ID is only meaningful when
* conversation archiving is enabled. Therefore, this method returns <tt>-1</tt> if
* archiving is not turned on.
*
* @return the unique ID of the conversation, or <tt>-1</tt> if conversation
* archiving is not enabled.
*/
public long getConversationID() {
return conversationID;
}
/**
* Returns the JID of the room where the group conversation took place. If the conversation
* was a one-to-one chat then a <tt>null</tt> value is returned.
*
* @return the JID of room or null if this was a one-to-one chat.
*/
public JID getRoom() {
return room;
}
/**
* Returns the conversation participants.
*
* @return the two conversation participants. Returned JIDs are full JIDs.
*/
public Collection<JID> getParticipants() {
List<JID> users = new ArrayList<JID>();
for (String key : participants.keySet()) {
users.add(new JID(key));
}
return users;
}
/**
* Returns the participations of the specified user (full JID) in this conversation. Each
* participation will hold the time when the user joined and left the conversation and the
* nickname if the room happened in a room.
*
* @param user the full JID of the user.
* @return the participations of the specified user (full JID) in this conversation.
*/
public Collection<ConversationParticipation> getParticipations(JID user) {
UserParticipations userParticipations = participants.get(user.toString());
if (userParticipations == null) {
return Collections.emptyList();
}
return userParticipations.getParticipations();
}
/**
* Returns true if one of the conversation participants is on an external server.
*
* @return true if one of the conversation participants is on an external server.
*/
public boolean isExternal() {
return external;
}
/**
* Returns the starting timestamp of the conversation.
*
* @return the start date.
*/
public Date getStartDate() {
return startDate;
}
/**
* Returns the timestamp the last message was receieved.
*
* @return the last activity.
*/
public Date getLastActivity() {
return lastActivity;
}
/**
* Returns the number of messages that make up the conversation.
*
* @return the message count.
*/
public int getMessageCount() {
return messageCount;
}
/**
* Returns the archived messages in the conversation. If message archiving is not
* enabled, this method will always return an empty collection. This method will only
* return messages that have already been batch-archived to the database; in other
* words, it does not provide a real-time view of new messages.
*
* @return the archived messages in the conversation.
*/
public List<ArchivedMessage> getMessages() {
if (room == null && !conversationManager.isMessageArchivingEnabled()) {
return Collections.emptyList();
}
else if (room != null && !conversationManager.isRoomArchivingEnabled()) {
return Collections.emptyList();
}
List<ArchivedMessage> messages = new ArrayList<ArchivedMessage>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_MESSAGES);
pstmt.setLong(1, getConversationID());
rs = pstmt.executeQuery();
while (rs.next()) {
JID fromJID = new JID(rs.getString(1));
JID toJID = new JID(rs.getString(2));
Date date = new Date(rs.getLong(3));
String body = DbConnectionManager.getLargeTextField(rs, 4);
messages.add(new ArchivedMessage(conversationID, fromJID, toJID, date, body, false));
}
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
// Add messages of users joining or leaving the group chat conversation
if (room != null) {
for (Map.Entry<String, UserParticipations> entry : participants.entrySet()) {
JID user = new JID(entry.getKey());
boolean anonymous = false;
String name;
try {
name = UserNameManager.getUserName(user);
}
catch (UserNotFoundException e) {
name = user.toBareJID();
anonymous = true;
}
for (ConversationParticipation participation : entry.getValue().getParticipations()) {
if (participation.getJoined() == null) {
Log.warn("Found muc participant with no join date in conversation: " + conversationID);
continue;
}
JID jid = new JID(room + "/" + participation.getNickname());
String joinBody;
String leftBody;
if (anonymous) {
joinBody = LocaleUtils.getLocalizedString("muc.conversation.joined.anonymous", MonitoringConstants.NAME,
Arrays.asList(participation.getNickname()));
leftBody = LocaleUtils.getLocalizedString("muc.conversation.left.anonymous", MonitoringConstants.NAME,
Arrays.asList(participation.getNickname()));
}
else {
joinBody = LocaleUtils.getLocalizedString("muc.conversation.joined", MonitoringConstants.NAME,
Arrays.asList(participation.getNickname(), name));
leftBody = LocaleUtils.getLocalizedString("muc.conversation.left", MonitoringConstants.NAME,
Arrays.asList(participation.getNickname(), name));
}
messages.add(
new ArchivedMessage(conversationID, user, jid, participation.getJoined(), joinBody, true));
if (participation.getLeft() != null) {
messages.add(new ArchivedMessage(conversationID, user, jid, participation.getLeft(), leftBody,
true));
}
}
}
// Sort messages by sent date
Collections.sort(messages, new Comparator<ArchivedMessage>() {
public int compare(ArchivedMessage o1, ArchivedMessage o2) {
return o1.getSentDate().compareTo(o2.getSentDate());
}
});
}
return messages;
}
@Override
private static final String INSERT_CONVERSATION = "INSERT INTO ofConversation(conversationID, room, isExternal, startDate, "
+ "lastActivity, messageCount) VALUES (?,?,?,?,?,0)";
private static final String INSERT_PARTICIPANT = "INSERT INTO ofConParticipant(conversationID, joinedDate, bareJID, jidResource, nickname) "
+ "VALUES (?,?,?,?,?)";
private static final String LOAD_CONVERSATION = "SELECT room, isExternal, startDate, lastActivity, messageCount "
+ "FROM ofConversation WHERE conversationID=?";
private static final String LOAD_PARTICIPANTS = "SELECT bareJID, jidResource, nickname, joinedDate, leftDate FROM ofConParticipant "
+ "WHERE conversationID=? ORDER BY joinedDate";
private static final String LOAD_MESSAGES = "SELECT fromJID, fromJIDResource, toJID, toJIDResource, sentDate, body FROM ofMessageArchive WHERE conversationID=? "
+ "ORDER BY sentDate";
private transient ConversationManager conversationManager;
private long conversationID = -1;
private Map<String, UserParticipations> participants;
private boolean external;
private Date startDate;
private Date lastActivity;
private int messageCount;
/**
* Room where the group conversion is taking place. For one-to-one chats there is no room so this variable will be null.
*/
private JID room;
/**
* Do not use this constructor. It only exists for serialization purposes.
*/
public Conversation() {
}
/**
* Constructs a new one-to-one conversation.
*
* @param conversationManager
* the ConversationManager.
* @param users
* the two participants in the conversation.
* @param external
* true if the conversation includes a user on another server.
* @param startDate
* the starting date of the conversation.
*/
public Conversation(ConversationManager conversationManager, Collection<JID> users, boolean external, Date startDate) {
if (users.size() != 2) {
throw new IllegalArgumentException("Illegal number of participants: " + users.size());
}
this.conversationManager = conversationManager;
this.participants = new HashMap<String, UserParticipations>(2);
// Ensure that we're use the full JID of each participant.
for (JID user : users) {
UserParticipations userParticipations = new UserParticipations(false);
userParticipations.addParticipation(new ConversationParticipation(startDate));
participants.put(user.toString(), userParticipations);
}
this.external = external;
this.startDate = startDate;
this.lastActivity = startDate;
// If archiving is enabled, insert the conversation into the database.
if (conversationManager.isMetadataArchivingEnabled()) {
try {
insertIntoDb();
} catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
}
/**
* Constructs a new group chat conversation that is taking place in a room.
*
* @param conversationManager
* the ConversationManager.
* @param room
* the JID of the room where the conversation is taking place.
* @param external
* true if the conversation includes a user on another server.
* @param startDate
* the starting date of the conversation.
*/
public Conversation(ConversationManager conversationManager, JID room, boolean external, Date startDate) {
this.conversationManager = conversationManager;
this.participants = new ConcurrentHashMap<String, UserParticipations>();
// Add list of existing room occupants as participants of this conversation
MUCRoom mucRoom = XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService(room).getChatRoom(room.getNode());
if (mucRoom != null) {
for (MUCRole role : mucRoom.getOccupants()) {
UserParticipations userParticipations = new UserParticipations(true);
userParticipations.addParticipation(new ConversationParticipation(startDate, role.getNickname()));
participants.put(role.getUserAddress().toString(), userParticipations);
}
}
this.room = room;
this.external = external;
this.startDate = startDate;
this.lastActivity = startDate;
// If archiving is enabled, insert the conversation into the database.
if (conversationManager.isMetadataArchivingEnabled()) {
try {
insertIntoDb();
} catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
}
/**
* Loads a conversation from the database.
*
* @param conversationManager
* the conversation manager.
* @param conversationID
* the ID of the conversation.
* @throws NotFoundException
* if the conversation can't be loaded.
*/
public Conversation(ConversationManager conversationManager, long conversationID) throws NotFoundException {
this.conversationManager = conversationManager;
this.conversationID = conversationID;
loadFromDb();
}
/**
* Returns the unique ID of the conversation. A unique ID is only meaningful when conversation archiving is enabled. Therefore, this method
* returns <tt>-1</tt> if archiving is not turned on.
*
* @return the unique ID of the conversation, or <tt>-1</tt> if conversation archiving is not enabled.
*/
public long getConversationID() {
return conversationID;
}
/**
* Returns the JID of the room where the group conversation took place. If the conversation was a one-to-one chat then a <tt>null</tt> value is
* returned.
*
* @return the JID of room or null if this was a one-to-one chat.
*/
public JID getRoom() {
return room;
}
/**
* Returns the conversation participants.
*
* @return the two conversation participants. Returned JIDs are full JIDs.
*/
public Collection<JID> getParticipants() {
List<JID> users = new ArrayList<JID>();
for (String key : participants.keySet()) {
users.add(new JID(key));
}
return users;
}
/**
* Returns the participations of the specified user (full JID) in this conversation. Each participation will hold the time when the user joined
* and left the conversation and the nickname if the room happened in a room.
*
* @param user
* the full JID of the user.
* @return the participations of the specified user (full JID) in this conversation.
*/
public Collection<ConversationParticipation> getParticipations(JID user) {
UserParticipations userParticipations = participants.get(user.toString());
if (userParticipations == null) {
return Collections.emptyList();
}
return userParticipations.getParticipations();
}
/**
* Returns true if one of the conversation participants is on an external server.
*
* @return true if one of the conversation participants is on an external server.
*/
public boolean isExternal() {
return external;
}
/**
* Returns the starting timestamp of the conversation.
*
* @return the start date.
*/
public Date getStartDate() {
return startDate;
}
/**
* Returns the timestamp the last message was receieved.
*
* @return the last activity.
*/
public Date getLastActivity() {
return lastActivity;
}
/**
* Returns the number of messages that make up the conversation.
*
* @return the message count.
*/
public int getMessageCount() {
return messageCount;
}
/**
* Returns the archived messages in the conversation. If message archiving is not enabled, this method will always return an empty collection.
* This method will only return messages that have already been batch-archived to the database; in other words, it does not provide a real-time
* view of new messages.
*
* @return the archived messages in the conversation.
*/
public List<ArchivedMessage> getMessages() {
if (room == null && !conversationManager.isMessageArchivingEnabled()) {
return Collections.emptyList();
} else if (room != null && !conversationManager.isRoomArchivingEnabled()) {
return Collections.emptyList();
}
List<ArchivedMessage> messages = new ArrayList<ArchivedMessage>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_MESSAGES);
pstmt.setLong(1, getConversationID());
rs = pstmt.executeQuery();
while (rs.next()) {
JID fromJID = new JID(rs.getString(1));
String fromJIDResource = rs.getString(2);
if (fromJIDResource != null && !"".equals(fromJIDResource)) {
fromJID = new JID(rs.getString(1) + "/" + fromJIDResource);
}
JID toJID = new JID(rs.getString(3));
String toJIDResource = rs.getString(4);
if (toJIDResource != null && !"".equals(toJIDResource)) {
toJID = new JID(rs.getString(1) + "/" + toJIDResource);
}
Date date = new Date(rs.getLong(5));
String body = DbConnectionManager.getLargeTextField(rs, 6);
messages.add(new ArchivedMessage(conversationID, fromJID, toJID, date, body, false));
}
} catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
} finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
// Add messages of users joining or leaving the group chat conversation
if (room != null) {
for (Map.Entry<String, UserParticipations> entry : participants.entrySet()) {
JID user = new JID(entry.getKey());
boolean anonymous = false;
String name;
try {
name = UserNameManager.getUserName(user);
} catch (UserNotFoundException e) {
name = user.toBareJID();
anonymous = true;
}
for (ConversationParticipation participation : entry.getValue().getParticipations()) {
if (participation.getJoined() == null) {
Log.warn("Found muc participant with no join date in conversation: " + conversationID);
continue;
}
JID jid = new JID(room + "/" + participation.getNickname());
String joinBody;
String leftBody;
if (anonymous) {
joinBody = LocaleUtils.getLocalizedString("muc.conversation.joined.anonymous", MonitoringConstants.NAME,
Arrays.asList(participation.getNickname()));
leftBody = LocaleUtils.getLocalizedString("muc.conversation.left.anonymous", MonitoringConstants.NAME,
Arrays.asList(participation.getNickname()));
} else {
joinBody = LocaleUtils.getLocalizedString("muc.conversation.joined", MonitoringConstants.NAME,
Arrays.asList(participation.getNickname(), name));
leftBody = LocaleUtils.getLocalizedString("muc.conversation.left", MonitoringConstants.NAME,
Arrays.asList(participation.getNickname(), name));
}
messages.add(new ArchivedMessage(conversationID, user, jid, participation.getJoined(), joinBody, true));
if (participation.getLeft() != null) {
messages.add(new ArchivedMessage(conversationID, user, jid, participation.getLeft(), leftBody, true));
}
}
}
// Sort messages by sent date
Collections.sort(messages, new Comparator<ArchivedMessage>() {
public int compare(ArchivedMessage o1, ArchivedMessage o2) {
return o1.getSentDate().compareTo(o2.getSentDate());
}
});
}
return messages;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("Conversation [").append(conversationID).append("]");
if (room != null) {
buf.append(" in room").append(room);
}
buf.append(" between ").append(participants);
buf.append(". started ").append(JiveGlobals.formatDateTime(startDate));
buf.append(", last active ").append(JiveGlobals.formatDateTime(lastActivity));
buf.append(". Total messages: ").append(messageCount);
return buf.toString();
}
/**
* Called when a new message for the conversation is received. Each time a new
* message is received, the last activity date will be updated and the message
* count incremented.
*
* @param entity JID of the entity that sent the message.
* @param date the date the message was sent.
*/
synchronized void messageReceived(JID entity, Date date) {
lastActivity = date;
messageCount++;
}
synchronized void participantJoined(JID user, String nickname, long timestamp) {
// Add the sender of the message as a participant of this conversation. If the sender
// was already a participant then he/she will appear just once. Rooms are never considered
// as participants
UserParticipations userParticipations = participants.get(user.toString());
if (userParticipations == null) {
userParticipations = new UserParticipations(true);
participants.put(user.toString(), userParticipations);
}
else {
// Get last known participation and check that the user has finished it
ConversationParticipation lastParticipation = userParticipations.getRecentParticipation();
if (lastParticipation != null && lastParticipation.getLeft() == null) {
Log.warn("Found user that never left a previous conversation: " + user);
lastParticipation.participationEnded(new Date(timestamp));
// Queue storeage of updated participation information
conversationManager.queueParticipantLeft(this, user, lastParticipation);
}
}
ConversationParticipation newParticipation = new ConversationParticipation(new Date(timestamp), nickname);
// Add element to the beginning of the list
userParticipations.addParticipation(newParticipation);
// If archiving is enabled, insert the conversation into the database (if not persistent yet).
if (conversationManager.isMetadataArchivingEnabled()) {
try {
if (conversationID == -1) {
// Save new conversation to the database
insertIntoDb();
}
else {
// Store new participation information
insertIntoDb(user, nickname, timestamp);
}
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
}
synchronized void participantLeft(JID user, long timestamp) {
// Get the list of participations of the specified user
UserParticipations userParticipations = participants.get(user.toString());
if (userParticipations == null) {
Log.warn("Found user that left a conversation but never started it: " + user);
}
else {
// Get last known participation and check that the user has not finished it
ConversationParticipation currentParticipation = userParticipations.getRecentParticipation();
if (currentParticipation == null || currentParticipation.getLeft() != null) {
Log.warn("Found user that left a conversation but never started it: " + user);
}
else {
currentParticipation.participationEnded(new Date(timestamp));
// Queue storeage of updated participation information
conversationManager.queueParticipantLeft(this, user, currentParticipation);
}
}
}
/**
* Inserts a new conversation into the database.
*
* @throws SQLException if an error occurs inserting the conversation.
*/
private void insertIntoDb() throws SQLException {
this.conversationID = SequenceManager.nextID(this);
Connection con = null;
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
PreparedStatement pstmt = con.prepareStatement(INSERT_CONVERSATION);
pstmt.setLong(1, conversationID);
pstmt.setString(2, room == null ? null : room.toString());
pstmt.setInt(3, (external ? 1 : 0));
pstmt.setLong(4, startDate.getTime());
pstmt.setLong(5, lastActivity.getTime());
pstmt.executeUpdate();
pstmt.close();
pstmt = con.prepareStatement(INSERT_PARTICIPANT);
for (Map.Entry<String, UserParticipations> entry : participants.entrySet()) {
JID user = new JID(entry.getKey());
for (ConversationParticipation participation : entry.getValue().getParticipations()) {
pstmt.setLong(1, conversationID);
pstmt.setLong(2, participation.getJoined().getTime());
pstmt.setString(3, user.toBareJID());
pstmt.setString(4, user.getResource() == null ? "" : user.getResource());
pstmt.setString(5, participation.getNickname());
pstmt.executeUpdate();
}
}
pstmt.close();
}
catch (SQLException sqle) {
abortTransaction = true;
throw sqle;
}
finally {
DbConnectionManager.closeTransactionConnection(con, abortTransaction);
}
}
/**
* Adds a new conversation participant into the database.
*
* @param participant the full JID of the participant.
* @param nickname nickname of the user in the room.
* @param joined timestamp when user joined the conversation.
* @throws SQLException if an error occurs inserting the conversation.
*/
private void insertIntoDb(JID participant, String nickname, long joined) throws SQLException {
Connection con = null;
try {
con = DbConnectionManager.getConnection();
PreparedStatement pstmt = con.prepareStatement(INSERT_PARTICIPANT);
pstmt.setLong(1, conversationID);
pstmt.setLong(2, joined);
pstmt.setString(3, participant.toBareJID());
pstmt.setString(4, participant.getResource() == null ? "" : participant.getResource());
pstmt.setString(5, nickname);
pstmt.executeUpdate();
pstmt.close();
}
catch (SQLException sqle) {
throw sqle;
}
finally {
DbConnectionManager.closeConnection(con);
}
}
private void loadFromDb() throws NotFoundException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_CONVERSATION);
pstmt.setLong(1, conversationID);
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new NotFoundException("Conversation not found: " + conversationID);
}
this.room = rs.getString(1) == null ? null : new JID(rs.getString(1));
this.external = rs.getInt(2) == 1;
this.startDate = new Date(rs.getLong(3));
this.lastActivity = new Date(rs.getLong(4));
this.messageCount = rs.getInt(5);
rs.close();
pstmt.close();
this.participants = new ConcurrentHashMap<String, UserParticipations>();
pstmt = con.prepareStatement(LOAD_PARTICIPANTS);
pstmt.setLong(1, conversationID);
rs = pstmt.executeQuery();
while (rs.next()) {
// Rebuild full JID of participant
String baredJID = rs.getString(1);
String resource = rs.getString(2);
JID fullJID = new JID("".equals(resource) ? baredJID : baredJID + "/" + resource);
// Rebuild joined and left time
ConversationParticipation participation =
new ConversationParticipation(new Date(rs.getLong(4)), rs.getString(3));
if (rs.getLong(5) > 0) {
participation.participationEnded(new Date(rs.getLong(5)));
}
// Store participation data
UserParticipations userParticipations = participants.get(fullJID.toString());
if (userParticipations == null) {
userParticipations = new UserParticipations(room != null);
participants.put(fullJID.toString(), userParticipations);
}
userParticipations.addParticipation(participation);
}
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
/**
* Notification message inficating that conversation has finished so remaining participants
* should be marked that they left the conversation.
*
* @param nowDate the date when the conversation was finished
*/
void conversationEnded(Date nowDate) {
for (Map.Entry<String, UserParticipations> entry : participants.entrySet()) {
ConversationParticipation currentParticipation = entry.getValue().getRecentParticipation();
if (currentParticipation.getLeft() == null) {
currentParticipation.participationEnded(nowDate);
// Queue storage of updated participation information
conversationManager.queueParticipantLeft(this, new JID(entry.getKey()), currentParticipation);
}
}
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeLong(out, conversationID);
ExternalizableUtil.getInstance().writeExternalizableMap(out, participants);
ExternalizableUtil.getInstance().writeBoolean(out, external);
ExternalizableUtil.getInstance().writeLong(out, startDate.getTime());
ExternalizableUtil.getInstance().writeLong(out, lastActivity.getTime());
ExternalizableUtil.getInstance().writeInt(out, messageCount);
ExternalizableUtil.getInstance().writeBoolean(out, room != null);
if (room != null) {
ExternalizableUtil.getInstance().writeSerializable(out, room);
}
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
MonitoringPlugin plugin = (MonitoringPlugin) XMPPServer.getInstance().getPluginManager().getPlugin(
MonitoringConstants.NAME);
conversationManager = (ConversationManager) plugin.getModule(ConversationManager.class);
this.participants = new ConcurrentHashMap<String, UserParticipations>();
conversationID = ExternalizableUtil.getInstance().readLong(in);
ExternalizableUtil.getInstance().readExternalizableMap(in, participants, getClass().getClassLoader());
external = ExternalizableUtil.getInstance().readBoolean(in);
startDate = new Date(ExternalizableUtil.getInstance().readLong(in));
lastActivity = new Date(ExternalizableUtil.getInstance().readLong(in));
messageCount = ExternalizableUtil.getInstance().readInt(in);
if (ExternalizableUtil.getInstance().readBoolean(in)) {
room = (JID) ExternalizableUtil.getInstance().readSerializable(in);
}
}
StringBuilder buf = new StringBuilder();
buf.append("Conversation [").append(conversationID).append("]");
if (room != null) {
buf.append(" in room").append(room);
}
buf.append(" between ").append(participants);
buf.append(". started ").append(JiveGlobals.formatDateTime(startDate));
buf.append(", last active ").append(JiveGlobals.formatDateTime(lastActivity));
buf.append(". Total messages: ").append(messageCount);
return buf.toString();
}
/**
* Called when a new message for the conversation is received. Each time a new message is received, the last activity date will be updated and the
* message count incremented.
*
* @param entity
* JID of the entity that sent the message.
* @param date
* the date the message was sent.
*/
synchronized void messageReceived(JID entity, Date date) {
lastActivity = date;
messageCount++;
}
synchronized void participantJoined(JID user, String nickname, long timestamp) {
// Add the sender of the message as a participant of this conversation. If the sender
// was already a participant then he/she will appear just once. Rooms are never considered
// as participants
UserParticipations userParticipations = participants.get(user.toString());
if (userParticipations == null) {
userParticipations = new UserParticipations(true);
participants.put(user.toString(), userParticipations);
} else {
// Get last known participation and check that the user has finished it
ConversationParticipation lastParticipation = userParticipations.getRecentParticipation();
if (lastParticipation != null && lastParticipation.getLeft() == null) {
Log.warn("Found user that never left a previous conversation: " + user);
lastParticipation.participationEnded(new Date(timestamp));
// Queue storeage of updated participation information
conversationManager.queueParticipantLeft(this, user, lastParticipation);
}
}
ConversationParticipation newParticipation = new ConversationParticipation(new Date(timestamp), nickname);
// Add element to the beginning of the list
userParticipations.addParticipation(newParticipation);
// If archiving is enabled, insert the conversation into the database (if not persistent yet).
if (conversationManager.isMetadataArchivingEnabled()) {
try {
if (conversationID == -1) {
// Save new conversation to the database
insertIntoDb();
} else {
// Store new participation information
insertIntoDb(user, nickname, timestamp);
}
} catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
}
synchronized void participantLeft(JID user, long timestamp) {
// Get the list of participations of the specified user
UserParticipations userParticipations = participants.get(user.toString());
if (userParticipations == null) {
Log.warn("Found user that left a conversation but never started it: " + user);
} else {
// Get last known participation and check that the user has not finished it
ConversationParticipation currentParticipation = userParticipations.getRecentParticipation();
if (currentParticipation == null || currentParticipation.getLeft() != null) {
Log.warn("Found user that left a conversation but never started it: " + user);
} else {
currentParticipation.participationEnded(new Date(timestamp));
// Queue storeage of updated participation information
conversationManager.queueParticipantLeft(this, user, currentParticipation);
}
}
}
/**
* Inserts a new conversation into the database.
*
* @throws SQLException
* if an error occurs inserting the conversation.
*/
private void insertIntoDb() throws SQLException {
this.conversationID = SequenceManager.nextID(this);
Connection con = null;
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
PreparedStatement pstmt = con.prepareStatement(INSERT_CONVERSATION);
pstmt.setLong(1, conversationID);
pstmt.setString(2, room == null ? null : room.toString());
pstmt.setInt(3, (external ? 1 : 0));
pstmt.setLong(4, startDate.getTime());
pstmt.setLong(5, lastActivity.getTime());
pstmt.executeUpdate();
pstmt.close();
pstmt = con.prepareStatement(INSERT_PARTICIPANT);
for (Map.Entry<String, UserParticipations> entry : participants.entrySet()) {
JID user = new JID(entry.getKey());
for (ConversationParticipation participation : entry.getValue().getParticipations()) {
pstmt.setLong(1, conversationID);
pstmt.setLong(2, participation.getJoined().getTime());
pstmt.setString(3, user.toBareJID());
pstmt.setString(4, user.getResource() == null ? "" : user.getResource());
pstmt.setString(5, participation.getNickname());
pstmt.executeUpdate();
}
}
pstmt.close();
} catch (SQLException sqle) {
abortTransaction = true;
throw sqle;
} finally {
DbConnectionManager.closeTransactionConnection(con, abortTransaction);
}
}
/**
* Adds a new conversation participant into the database.
*
* @param participant
* the full JID of the participant.
* @param nickname
* nickname of the user in the room.
* @param joined
* timestamp when user joined the conversation.
* @throws SQLException
* if an error occurs inserting the conversation.
*/
private void insertIntoDb(JID participant, String nickname, long joined) throws SQLException {
Connection con = null;
try {
con = DbConnectionManager.getConnection();
PreparedStatement pstmt = con.prepareStatement(INSERT_PARTICIPANT);
pstmt.setLong(1, conversationID);
pstmt.setLong(2, joined);
pstmt.setString(3, participant.toBareJID());
pstmt.setString(4, participant.getResource() == null ? "" : participant.getResource());
pstmt.setString(5, nickname);
pstmt.executeUpdate();
pstmt.close();
} catch (SQLException sqle) {
throw sqle;
} finally {
DbConnectionManager.closeConnection(con);
}
}
private void loadFromDb() throws NotFoundException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_CONVERSATION);
pstmt.setLong(1, conversationID);
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new NotFoundException("Conversation not found: " + conversationID);
}
this.room = rs.getString(1) == null ? null : new JID(rs.getString(1));
this.external = rs.getInt(2) == 1;
this.startDate = new Date(rs.getLong(3));
this.lastActivity = new Date(rs.getLong(4));
this.messageCount = rs.getInt(5);
rs.close();
pstmt.close();
this.participants = new ConcurrentHashMap<String, UserParticipations>();
pstmt = con.prepareStatement(LOAD_PARTICIPANTS);
pstmt.setLong(1, conversationID);
rs = pstmt.executeQuery();
while (rs.next()) {
// Rebuild full JID of participant
String baredJID = rs.getString(1);
String resource = rs.getString(2);
JID fullJID = new JID("".equals(resource) ? baredJID : baredJID + "/" + resource);
// Rebuild joined and left time
ConversationParticipation participation = new ConversationParticipation(new Date(rs.getLong(4)), rs.getString(3));
if (rs.getLong(5) > 0) {
participation.participationEnded(new Date(rs.getLong(5)));
}
// Store participation data
UserParticipations userParticipations = participants.get(fullJID.toString());
if (userParticipations == null) {
userParticipations = new UserParticipations(room != null);
participants.put(fullJID.toString(), userParticipations);
}
userParticipations.addParticipation(participation);
}
} catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
} finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
/**
* Notification message inficating that conversation has finished so remaining participants should be marked that they left the conversation.
*
* @param nowDate
* the date when the conversation was finished
*/
void conversationEnded(Date nowDate) {
for (Map.Entry<String, UserParticipations> entry : participants.entrySet()) {
ConversationParticipation currentParticipation = entry.getValue().getRecentParticipation();
if (currentParticipation.getLeft() == null) {
currentParticipation.participationEnded(nowDate);
// Queue storage of updated participation information
conversationManager.queueParticipantLeft(this, new JID(entry.getKey()), currentParticipation);
}
}
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeLong(out, conversationID);
ExternalizableUtil.getInstance().writeExternalizableMap(out, participants);
ExternalizableUtil.getInstance().writeBoolean(out, external);
ExternalizableUtil.getInstance().writeLong(out, startDate.getTime());
ExternalizableUtil.getInstance().writeLong(out, lastActivity.getTime());
ExternalizableUtil.getInstance().writeInt(out, messageCount);
ExternalizableUtil.getInstance().writeBoolean(out, room != null);
if (room != null) {
ExternalizableUtil.getInstance().writeSerializable(out, room);
}
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
MonitoringPlugin plugin = (MonitoringPlugin) XMPPServer.getInstance().getPluginManager().getPlugin(MonitoringConstants.NAME);
conversationManager = (ConversationManager) plugin.getModule(ConversationManager.class);
this.participants = new ConcurrentHashMap<String, UserParticipations>();
conversationID = ExternalizableUtil.getInstance().readLong(in);
ExternalizableUtil.getInstance().readExternalizableMap(in, participants, getClass().getClassLoader());
external = ExternalizableUtil.getInstance().readBoolean(in);
startDate = new Date(ExternalizableUtil.getInstance().readLong(in));
lastActivity = new Date(ExternalizableUtil.getInstance().readLong(in));
messageCount = ExternalizableUtil.getInstance().readInt(in);
if (ExternalizableUtil.getInstance().readBoolean(in)) {
room = (JID) ExternalizableUtil.getInstance().readSerializable(in);
}
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment