Skip to content

Commit

Permalink
For #120 / #170: Reworked XML serialization
Browse files Browse the repository at this point in the history
Improved the XML-based serialization that got introduced as a fix for #120/#170

Dropped Externalization (supposed to be replaced with XML-based serialization) to reduce complexity.

Added unit tests to verify XML-based serialization functionality.
  • Loading branch information
guusdk committed Dec 23, 2021
1 parent 6092da2 commit 4cfc7ba
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 64 deletions.
13 changes: 13 additions & 0 deletions src/java/org/jivesoftware/openfire/archive/Conversation.java
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,19 @@ void conversationEnded(ConversationManager conversationManager, Date nowDate) {
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Conversation that = (Conversation) o;
return conversationID == that.conversationID && external == that.external && messageCount == that.messageCount && Objects.equals(participants, that.participants) && Objects.equals(startDate, that.startDate) && Objects.equals(lastActivity, that.lastActivity) && Objects.equals(room, that.room);
}

@Override
public int hashCode() {
return Objects.hash(conversationID, participants, external, startDate, lastActivity, messageCount, room);
}

/**
* Convert the conversation to an XML representation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,6 @@ public class ConversationManager implements ComponentEventListener{
private ConversationEventsQueue conversationEventsQueue;
private TaskEngine taskEngine;

private static XmlSerializer xmlSerializer;


private Map<String, Conversation> conversations = new ConcurrentHashMap<>();
private boolean metadataArchivingEnabled;
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,10 @@

package org.jivesoftware.openfire.archive;

import org.jivesoftware.util.cache.ExternalizableUtil;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Date;
import java.util.Objects;

/**
* Participation of a user, connected from a specific resource, in a conversation. If
Expand All @@ -34,11 +29,14 @@
* @author Gaston Dombiak
*/
@XmlRootElement
public class ConversationParticipation implements Externalizable {
public class ConversationParticipation {

@XmlElement
private Date joined = new Date();

@XmlElement
private Date left;

@XmlElement
private String nickname;

Expand Down Expand Up @@ -86,25 +84,16 @@ public String getNickname() {
return nickname;
}

public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeLong(out, joined.getTime());
ExternalizableUtil.getInstance().writeBoolean(out, nickname != null);
if (nickname != null) {
ExternalizableUtil.getInstance().writeSafeUTF(out, nickname);
}
ExternalizableUtil.getInstance().writeBoolean(out, left != null);
if (left != null) {
ExternalizableUtil.getInstance().writeLong(out, left.getTime());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ConversationParticipation that = (ConversationParticipation) o;
return Objects.equals(joined, that.joined) && Objects.equals(left, that.left) && Objects.equals(nickname, that.nickname);
}

public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
joined = new Date(ExternalizableUtil.getInstance().readLong(in));
if (ExternalizableUtil.getInstance().readBoolean(in)) {
nickname = ExternalizableUtil.getInstance().readSafeUTF(in);
}
if (ExternalizableUtil.getInstance().readBoolean(in)) {
left = new Date(ExternalizableUtil.getInstance().readLong(in));
}
@Override
public int hashCode() {
return Objects.hash(joined, left, nickname);
}
}
51 changes: 15 additions & 36 deletions src/java/org/jivesoftware/openfire/archive/UserParticipations.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,21 @@
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* Created by IntelliJ IDEA.
* User: gato
* Date: Oct 9, 2007
* Time: 11:59:42 PM
* To change this template use File | Settings | File Templates.
* @author Gastion Dombiak
*/
@XmlRootElement
public class UserParticipations implements Externalizable {
public class UserParticipations {
/**
* Flag that indicates if the participations of the user were in a group chat conversation or a one-to-one
* chat.
*/
@XmlElement
private boolean roomParticipation;

/**
* Participations of the same user in a groupchat or one-to-one chat. In a group chat conversation
* a user may leave the conversation and return later so for each time the user joined the room a new
Expand All @@ -58,10 +56,10 @@ public UserParticipations() {
public UserParticipations(boolean roomParticipation) {
this.roomParticipation = roomParticipation;
if (roomParticipation) {
participations = new ArrayList<ConversationParticipation>();
participations = new ArrayList<>();
}
else {
participations = new CopyOnWriteArrayList<ConversationParticipation>();
participations = new CopyOnWriteArrayList<>();
}
}

Expand All @@ -77,35 +75,16 @@ public void addParticipation(ConversationParticipation participation) {
participations.add(0, participation);
}

public void writeExternal(ObjectOutput out) throws IOException {
// ClassCastExceptions occur when using classes provided by a plugin during serialization (sometimes only after
// reloading the plugin without restarting Openfire. This is why this implementation marshalls data as XML when
// serializing. See https://github.com/igniterealtime/openfire-monitoring-plugin/issues/120
// and https://github.com/igniterealtime/openfire-monitoring-plugin/issues/156
ExternalizableUtil.getInstance().writeBoolean(out, roomParticipation);
ExternalizableUtil.getInstance().writeInt(out, participations.size());
for (ConversationParticipation cp : participations) {
ExternalizableUtil.getInstance().writeSafeUTF(out, XmlSerializer.getInstance().marshall(cp));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserParticipations that = (UserParticipations) o;
return roomParticipation == that.roomParticipation && Objects.equals(participations, that.participations);
}

public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// ClassCastExceptions occur when using classes provided by a plugin during serialization (sometimes only after
// reloading the plugin without restarting Openfire. This is why this implementation marshalls data as XML when
// serializing. See https://github.com/igniterealtime/openfire-monitoring-plugin/issues/120
// and https://github.com/igniterealtime/openfire-monitoring-plugin/issues/156
roomParticipation = ExternalizableUtil.getInstance().readBoolean(in);
if (roomParticipation) {
participations = new ArrayList<>();
}
else {
participations = new CopyOnWriteArrayList<>();
}
int participationCount = ExternalizableUtil.getInstance().readInt(in);
for (int i = 0; i < participationCount; i++) {
String marshalledConversationParticipation = ExternalizableUtil.getInstance().readSafeUTF(in);
final ConversationParticipation unmarshalledParticipation = (ConversationParticipation)XmlSerializer.getInstance().unmarshall(marshalledConversationParticipation);
participations.add(unmarshalledParticipation);
}
@Override
public int hashCode() {
return Objects.hash(roomParticipation, participations);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.xmpp.packet.JID;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
Expand All @@ -30,6 +32,74 @@
*/
public class XmlSerializerTest {

/**
* Checks that an instance of {@link Conversation} can be marshalled to XML, and back to an object again,
* verifying that the resulting object is equal to the original input.
*/
@Test
public void testXmlMarshallingConversationTest() throws Exception {
// Setup test fixture.
final Map<String, UserParticipations> participations = new HashMap<>();
final UserParticipations userParticipations = new UserParticipations(true);
userParticipations.addParticipation(new ConversationParticipation(new Date(2), "unittest"));
userParticipations.addParticipation(new ConversationParticipation(new Date(3)));
final ConversationParticipation participation = new ConversationParticipation(new Date(4), "unittest");
participation.participationEnded(new Date(5));
userParticipations.addParticipation(participation);
participations.put("g@d", userParticipations);
participations.put("a@s/f", userParticipations);
final Conversation input = new Conversation(new JID("[email protected]"), true, new Date(1), new Date(2), 8, participations);

// Execute system under test.
final String xml = XmlSerializer.getInstance().marshall(input);
final Object result = XmlSerializer.getInstance().unmarshall(xml);

// Verify result.
assertTrue(result instanceof Conversation);
assertEquals("Marshalled content didn't unmarshall as equal object. Marshalled content: " + xml, input, result);
}

/**
* Checks that an instance of {@link UserParticipations} can be marshalled to XML, and back to an object again,
* verifying that the resulting object is equal to the original input.
*/
@Test
public void testXmlMarshallingUserParticipationsTest() throws Exception {
// Setup test fixture.
final UserParticipations input = new UserParticipations(true);
input.addParticipation(new ConversationParticipation(new Date(2), "unittest"));
input.addParticipation(new ConversationParticipation(new Date(3)));
final ConversationParticipation participation = new ConversationParticipation(new Date(4), "unittest");
participation.participationEnded(new Date(5));
input.addParticipation(participation);

// Execute system under test.
final String xml = XmlSerializer.getInstance().marshall(input);
final Object result = XmlSerializer.getInstance().unmarshall(xml);

// Verify result.
assertTrue(result instanceof UserParticipations);
assertEquals("Marshalled content didn't unmarshall as equal object. Marshalled content: " + xml, input, result);
}

/**
* Checks that an instance of {@link ConversationParticipation} can be marshalled to XML, and back to an object again,
* verifying that the resulting object is equal to the original input.
*/
@Test
public void testXmlMarshallingConversationParticipationTest() throws Exception {
// Setup test fixture.
final ConversationParticipation input = new ConversationParticipation(new Date(2), "unittest");

// Execute system under test.
final String xml = XmlSerializer.getInstance().marshall(input);
final Object result = XmlSerializer.getInstance().unmarshall(xml);

// Verify result.
assertTrue(result instanceof ConversationParticipation);
assertEquals("Marshalled content didn't unmarshall as equal object. Marshalled content: " + xml, input, result);
}

/**
* Checks that an instance of {@link ConversationEvent} can be marshalled to XML, and back to an object again,
* verifying that the resulting object is equal to the original input.
Expand Down

0 comments on commit 4cfc7ba

Please sign in to comment.