Skip to content

Commit 52ba880

Browse files
committed
fixes #139: Refactor stanza construction
When a previously archived message is being retrieved, the optional 'stanza' database value is eventually parsed as XMPP. This commit brings forward this process (to a place immediately after the data is obtained from the database), replacing the existing mechanism that did it just before the message was being routed to the intended recipient. The benefit of this is that the code that it is more fail-fast, and operations that follow the database retrieval can work with a proper stanza, instead of having to work with its String representation. This brings type-safety. Lastly, there's less ambiguity around where parsing occurs (and at what point the parsing can be expected to have been completed).
1 parent db83b39 commit 52ba880

File tree

6 files changed

+143
-110
lines changed

6 files changed

+143
-110
lines changed

src/java/com/reucon/openfire/plugin/archive/impl/JdbcPersistenceManager.java

+16-40
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@
77
import com.reucon.openfire.plugin.archive.model.Participant;
88
import com.reucon.openfire.plugin.archive.util.StanzaIDUtil;
99
import com.reucon.openfire.plugin.archive.xep0059.XmppResultSet;
10-
import org.dom4j.Document;
11-
import org.dom4j.DocumentFactory;
12-
import org.dom4j.DocumentHelper;
13-
import org.dom4j.Element;
10+
import org.dom4j.*;
1411
import org.jivesoftware.database.DbConnectionManager;
1512
import org.jivesoftware.openfire.archive.ConversationManager;
1613
import org.jivesoftware.util.JiveConstants;
@@ -31,7 +28,6 @@
3128
*/
3229
public class JdbcPersistenceManager implements PersistenceManager {
3330
private static final Logger Log = LoggerFactory.getLogger( JdbcPersistenceManager.class );
34-
protected static final DocumentFactory docFactory = DocumentFactory.getInstance();
3531
public static final int DEFAULT_MAX = 1000;
3632

3733
public static final String SELECT_MESSAGES_BY_CONVERSATION = "SELECT DISTINCT ofConversation.conversationID, ofConversation.room, "
@@ -474,7 +470,7 @@ public Conversation getConversation(JID owner, JID with, Date start) {
474470
message = extractMessage(rs);
475471
conversation.addMessage(message);
476472
}
477-
} catch (SQLException sqle) {
473+
} catch (SQLException | DocumentException sqle) {
478474
Log.error("Error selecting conversation", sqle);
479475
} finally {
480476
DbConnectionManager.closeConnection(rs, pstmt, con);
@@ -542,7 +538,7 @@ private Collection<Participant> extractParticipant(ResultSet rs) throws SQLExcep
542538
return participants;
543539
}
544540

545-
static ArchivedMessage extractMessage(ResultSet rs) throws SQLException {
541+
static ArchivedMessage extractMessage(ResultSet rs) throws SQLException, DocumentException {
546542
Date time = millisToDate(rs.getLong("sentDate"));
547543
String body = rs.getString("body");
548544
String stanza = rs.getString("stanza");
@@ -604,46 +600,26 @@ public static ArchivedMessage getArchivedMessage( long messageId, JID owner )
604600
} catch (SQLException ex) {
605601
Log.warn("SQL failure while trying to get message with ID {} from the archive of {}.", messageId, owner, ex);
606602
return null;
603+
} catch (DocumentException ex) {
604+
Log.warn("Failure to parse 'stanza' value as XMPP for the message with ID {} from the archive of {}.", messageId, owner, ex);
605+
return null;
607606
} finally {
608607
DbConnectionManager.closeConnection(rs, pstmt, connection);
609608
}
610609
}
611610

612-
static protected ArchivedMessage asArchivedMessage(JID owner, String fromJID, String fromJIDResource, String toJID, String toJIDResource, Date sentDate, String body, String stanza, Long id)
613-
{
614-
if (stanza == null) {
615-
Message message = new Message();
616-
message.setFrom(fromJID);
617-
message.setTo(toJID);
618-
message.setType(Message.Type.normal);
619-
message.setBody(body);
620-
stanza = message.toString();
621-
}
622-
611+
static protected ArchivedMessage asArchivedMessage(JID owner, String fromJID, String fromJIDResource, String toJID, String toJIDResource, Date sentDate, String body, String stanza, Long id) throws DocumentException {
623612
String sid;
624-
try
625-
{
626-
if ( !JiveGlobals.getBooleanProperty( "conversation.OF-1804.disable", false ) )
627-
{
628-
// Prior to OF-1804 (Openfire 4.4.0), the stanza was logged with a formatter applied.
629-
// This causes message formatting to be modified (notably, new lines could be altered).
630-
// This workaround restores the original body text, that was stored in a different column.
631-
final int pos1 = stanza.indexOf( "<body>" );
632-
final int pos2 = stanza.indexOf( "</body>" );
633-
634-
if ( pos1 > -1 && pos2 > -1 )
635-
{
636-
// Add the body value to a proper XML element, so that the strings get XML encoded (eg: ampersand is escaped).
637-
final Element bodyEl = docFactory.createDocument().addElement("body");
638-
bodyEl.setText(body);
639-
stanza = stanza.substring( 0, pos1 ) + bodyEl.asXML() + stanza.substring( pos2 + 7 );
640-
}
613+
if (stanza != null) {
614+
try {
615+
final Document doc = DocumentHelper.parseText(stanza);
616+
final Message message = new Message(doc.getRootElement());
617+
sid = StanzaIDUtil.findFirstUniqueAndStableStanzaID(message, owner.toBareJID());
618+
} catch (Exception e) {
619+
Log.warn("An exception occurred while parsing message with ID {}", id, e);
620+
sid = null;
641621
}
642-
final Document doc = DocumentHelper.parseText( stanza );
643-
final Message message = new Message( doc.getRootElement() );
644-
sid = StanzaIDUtil.findFirstUniqueAndStableStanzaID( message, owner.toBareJID() );
645-
} catch ( Exception e ) {
646-
Log.warn( "An exception occurred while parsing message with ID {}", id, e );
622+
} else {
647623
sid = null;
648624
}
649625

src/java/com/reucon/openfire/plugin/archive/impl/MucMamPersistenceManager.java

+13-24
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,15 @@ public static ArchivedMessage getArchivedMessage( long messageId, MUCRoom room )
217217
} catch (SQLException ex) {
218218
Log.warn("SQL failure while trying to get message with ID {} from the archive of MUC room {}.", messageId, room, ex);
219219
return null;
220+
} catch (DocumentException ex) {
221+
Log.warn("Failure to parse 'stanza' value as XMPP for the message with ID {} from the archive of MUC room {}.", messageId, room, ex);
222+
return null;
220223
} finally {
221224
DbConnectionManager.closeConnection(rs, pstmt, connection);
222225
}
223226
}
224227

225-
static protected ArchivedMessage asArchivedMessage(JID roomJID, String senderJID, String nickname, Date sentDate, String subject, String body, String stanza, long id)
226-
{
228+
static protected ArchivedMessage asArchivedMessage(JID roomJID, String senderJID, String nickname, Date sentDate, String subject, String body, String stanza, long id) throws DocumentException {
227229
if (stanza == null) {
228230
Message message = new Message();
229231
message.setType(Message.Type.groupchat);
@@ -242,29 +244,16 @@ static protected ArchivedMessage asArchivedMessage(JID roomJID, String senderJID
242244
}
243245

244246
String sid;
245-
try
246-
{
247-
if ( !JiveGlobals.getBooleanProperty( "conversation.OF-1804.disable", false ) )
248-
{
249-
// Prior to OF-1804 (Openfire 4.4.0), the stanza was logged with a formatter applied.
250-
// This causes message formatting to be modified (notably, new lines could be altered).
251-
// This workaround restores the original body text, that was stored in a different column.
252-
final int pos1 = stanza.indexOf( "<body>" );
253-
final int pos2 = stanza.indexOf( "</body>" );
254-
255-
if ( pos1 > -1 && pos2 > -1 )
256-
{
257-
// Add the body value to a proper XML element, so that the strings get XML encoded (eg: ampersand is escaped).
258-
final Element bodyEl = docFactory.createDocument().addElement("body");
259-
bodyEl.setText(body);
260-
stanza = stanza.substring( 0, pos1 ) + bodyEl.asXML() + stanza.substring( pos2 + 7 );
261-
}
247+
if (stanza != null) {
248+
try {
249+
final Document doc = DocumentHelper.parseText(stanza);
250+
final Message message = new Message(doc.getRootElement());
251+
sid = StanzaIDUtil.findFirstUniqueAndStableStanzaID(message, roomJID.toBareJID());
252+
} catch (Exception e) {
253+
Log.warn("An exception occurred while parsing message with ID {}", id, e);
254+
sid = null;
262255
}
263-
final Document doc = DocumentHelper.parseText( stanza );
264-
final Message message = new Message( doc.getRootElement() );
265-
sid = StanzaIDUtil.findFirstUniqueAndStableStanzaID( message, roomJID.toBareJID() );
266-
} catch ( Exception e ) {
267-
Log.warn( "An exception occurred while parsing message with ID {}", id, e );
256+
} else {
268257
sid = null;
269258
}
270259

src/java/com/reucon/openfire/plugin/archive/impl/PaginatedMessageDatabaseQuery.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.reucon.openfire.plugin.archive.impl;
1616

1717
import com.reucon.openfire.plugin.archive.model.ArchivedMessage;
18+
import org.dom4j.DocumentException;
1819
import org.jivesoftware.database.DbConnectionManager;
1920
import org.slf4j.Logger;
2021
import org.slf4j.LoggerFactory;
@@ -134,7 +135,9 @@ protected List<ArchivedMessage> getPage( final Long after, final Long before, fi
134135
archivedMessages.add(archivedMessage);
135136
}
136137
} catch (SQLException e) {
137-
Log.error("SQL failure during MAM: ", e);
138+
Log.error("SQL failure during MAM for owner: {}", this.owner, e);
139+
} catch (DocumentException e) {
140+
Log.error("Unable to parse 'stanza' value as valid XMMP for owner {}", this.owner, e);
138141
} finally {
139142
DbConnectionManager.closeConnection(rs, pstmt, connection);
140143
}

src/java/com/reucon/openfire/plugin/archive/impl/PaginatedMucMessageDatabaseQuery.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.reucon.openfire.plugin.archive.impl;
1616

1717
import com.reucon.openfire.plugin.archive.model.ArchivedMessage;
18+
import org.dom4j.DocumentException;
1819
import org.jivesoftware.database.DbConnectionManager;
1920
import org.jivesoftware.openfire.muc.MUCRoom;
2021
import org.jivesoftware.util.StringUtils;
@@ -136,7 +137,9 @@ protected List<ArchivedMessage> getPage( final Long after, final Long before, fi
136137
msgs.add( MucMamPersistenceManager.asArchivedMessage(room.getJID(), senderJID, nickname, sentDate, subject, body, stanza, id) );
137138
}
138139
} catch (SQLException e) {
139-
Log.error("SQL failure during MAM-MUC: ", e);
140+
Log.error("SQL failure during MAM-MUC for room: {}", this.room, e);
141+
} catch (DocumentException e) {
142+
Log.error("Unable to parse 'stanza' value as valid XMMP for MAM-MUC room {}", this.room, e);
140143
} finally {
141144
DbConnectionManager.closeConnection(rs, pstmt, connection);
142145
}

src/java/com/reucon/openfire/plugin/archive/model/ArchivedMessage.java

+79-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package com.reucon.openfire.plugin.archive.model;
22

3+
import org.dom4j.*;
34
import org.jivesoftware.database.JiveID;
5+
import org.jivesoftware.openfire.XMPPServer;
6+
import org.jivesoftware.util.JiveGlobals;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
49
import org.xmpp.packet.JID;
10+
import org.xmpp.packet.Message;
511

612
import javax.annotation.Nonnull;
713
import javax.annotation.Nullable;
@@ -14,6 +20,9 @@
1420
@JiveID(601)
1521
@Immutable
1622
public class ArchivedMessage {
23+
24+
public static final Logger Log = LoggerFactory.getLogger( ArchivedMessage.class );
25+
1726
public enum Direction {
1827
/**
1928
* A message sent by the owner.
@@ -42,19 +51,33 @@ public enum Direction {
4251
private final JID with;
4352

4453
@Nullable
45-
private final String stanza;
54+
private final Message stanza;
4655

4756
@Nullable
4857
private final String stableId;
4958

50-
public ArchivedMessage(@Nullable final Long id, @Nonnull final Date time, @Nonnull final Direction direction, @Nullable final JID with, @Nullable final String stableId, @Nullable final String body, @Nullable final String stanza) {
59+
public ArchivedMessage(@Nullable final Long id, @Nonnull final Date time, @Nonnull final Direction direction, @Nullable final JID with, @Nullable final String stableId, @Nullable final String body, @Nullable final String stanza) throws DocumentException {
5160
this.id = id;
5261
this.time = time;
5362
this.direction = direction;
5463
this.with = with;
5564
this.stableId = stableId;
5665
this.body = body;
57-
this.stanza = stanza;
66+
67+
if ( stanza != null) {
68+
final Document doc = DocumentHelper.parseText( stanza );
69+
this.stanza = new Message( doc.getRootElement() );
70+
} else {
71+
this.stanza = null;
72+
}
73+
74+
if ( this.stanza != null && !JiveGlobals.getBooleanProperty( "conversation.OF-1804.disable", false ) )
75+
{
76+
// Prior to OF-1804 (Openfire 4.4.0), the stanza was logged with a formatter applied.
77+
// This causes message formatting to be modified (notably, new lines could be altered).
78+
// This workaround restores the original body text, that was stored in a different column.
79+
this.stanza.setBody( body );
80+
}
5881
}
5982

6083
/**
@@ -107,7 +130,7 @@ public String getBody() {
107130
* @return XMPP packet
108131
*/
109132
@Nullable
110-
public String getStanza() {
133+
public Message getStanza() {
111134
return stanza;
112135
}
113136

@@ -126,7 +149,8 @@ public JID getWith() {
126149
}
127150

128151
/**
129-
* The first stable and unique stanza-id value in the stanza, if the stanza contains such a value.
152+
* The first stable and unique stanza-id value in the stanza that was set by owner of the message archive, if the
153+
* stanza contains such a value.
130154
*
131155
* @return a stable and unique stanza-id value.
132156
*/
@@ -146,4 +170,54 @@ public String toString() {
146170

147171
return sb.toString();
148172
}
173+
174+
/**
175+
* When the archived message does not include the original stanza, this method can be used to recreate a stanza from
176+
* the individual parts that did get persisted.
177+
*
178+
* Note that the result of this method will not include most of the metadata that would have been sent in the original
179+
* stanza. When the original stanza is available, that should be preferred over the result of this method. This
180+
* method explicitly does not return or use a stanza that is available in the archivedMessage argument, assuming
181+
* that the caller has evaluated that, and choose (for whatever reason) to not use that (eg: it might be malformed?)
182+
*
183+
* When the archived message does not contain certain optional data (such as a body), this method cannot recreate a
184+
* message stanza. In such cases, this method returns null.
185+
*
186+
* @param archivedMessage The archived message for which to recreate a stanza.
187+
* @param archiveOwner The owner of the archive from which to recreate a stanza
188+
* @return A recreated stanza.
189+
*/
190+
@Nullable
191+
public static Message recreateStanza( @Nonnull final ArchivedMessage archivedMessage, @Nonnull final JID archiveOwner )
192+
{
193+
if ( archivedMessage.getBody() == null || archivedMessage.getBody().isEmpty() ) {
194+
Log.trace("Cannot reconstruct stanza for archived message ID {}, as it has no body.", archivedMessage.getId());
195+
return null;
196+
}
197+
198+
// Try creating a fake one from the body.
199+
final JID to;
200+
final JID from;
201+
if (archivedMessage.getDirection() == ArchivedMessage.Direction.to) {
202+
// message sent by the archive owner;
203+
to = archivedMessage.getWith();
204+
from = archiveOwner;
205+
} else {
206+
// message received by the archive owner;
207+
to = archiveOwner;
208+
from = archivedMessage.getWith();
209+
}
210+
211+
final boolean isMuc = (to != null && XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService( to ) != null)
212+
|| (from != null && XMPPServer.getInstance().getMultiUserChatManager().getMultiUserChatService( from ) != null);
213+
214+
final Message result = new Message();
215+
result.setFrom(from);
216+
result.setTo(to);
217+
result.setType( isMuc ? Message.Type.groupchat : Message.Type.chat );
218+
result.setBody(archivedMessage.getBody());
219+
220+
Log.trace( "Reconstructed stanza for archived message with ID {} (only a body was stored): {}", archivedMessage.getId(), result );
221+
return result;
222+
}
149223
}

0 commit comments

Comments
 (0)