diff --git a/documentation/messaging.md b/documentation/messaging.md
index 3ee5a4b0c..3aa595dae 100644
--- a/documentation/messaging.md
+++ b/documentation/messaging.md
@@ -17,7 +17,7 @@ and then send them a text message:
```
// Assume we've created an XMPPConnection name "connection"._
-ChatManager chatmanager = connection.getChatManager();
+ChatManager chatmanager = ChatManager.getInstanceFor(connection);
Chat newChat = chatmanager.createChat("jsmith@jivesoftware.com", new MessageListener() {
public void processMessage(Chat chat, Message message) {
System.out.println("Received message: " + message);
@@ -72,8 +72,9 @@ when it happens. You can register a message listener to receive all future
messages as part of this handler.
```
-_// Assume we've created an XMPPConnection name "connection"._
-ChatManager chatmanager = connection.getChatManager().addChatListener(
+// Assume we've created an XMPPConnection name "connection"._
+ChatManager chatManager = ChatManager.getInstanceFor(connection);
+chatManager.addChatListener(
new ChatManagerListener() {
@Override
public void chatCreated(Chat chat, boolean createdLocally)
diff --git a/resources/releasedocs/changelog.html b/resources/releasedocs/changelog.html
index 7a805d5f5..184c15f70 100644
--- a/resources/releasedocs/changelog.html
+++ b/resources/releasedocs/changelog.html
@@ -141,6 +141,19 @@ hr {
+
4.1.3 -- 2015-07-15
+
+
Bug
+
+
+- [SMACK-679] - Memory leak in Socks5BytestreamManager. Should use weak map for 'managers'
+
+- [SMACK-680] - XHTML bodies are un-escaped after parsing
+
+- [SMACK-681] - Roster presence callbacks may not be invoked right after login
+
+
+
4.1.2 -- 2015-06-27
Bug
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java
index 3363a725f..b7bd7f699 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java
@@ -477,7 +477,7 @@ public class PacketParserUtils {
}
break;
case XmlPullParser.TEXT:
- xml.append(parser.getText());
+ xml.escape(parser.getText());
break;
}
event = parser.next();
@@ -493,7 +493,12 @@ public class PacketParserUtils {
// Only append the text if the parser is not on on an empty element' start tag. Empty elements are reported
// twice, so in order to prevent duplication we only add their text when we are on their end tag.
if (!(event == XmlPullParser.START_TAG && parser.isEmptyElementTag())) {
- sb.append(parser.getText());
+ CharSequence text = parser.getText();
+ if (event == XmlPullParser.TEXT) {
+ // TODO the toString() can be removed in Smack 4.2.
+ text = StringUtils.escapeForXML(text.toString());
+ }
+ sb.append(text);
}
if (event == XmlPullParser.END_TAG && parser.getDepth() <= depth) {
break outerloop;
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java
index 57c69bc1a..d05e1577e 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java
@@ -20,13 +20,13 @@ import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;
@@ -113,7 +113,7 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream
private final static Random randomGenerator = new Random();
/* stores one Socks5BytestreamManager for each XMPP connection */
- private final static Map managers = new HashMap();
+ private final static Map managers = new WeakHashMap<>();
/*
* assigns a user to a listener that is informed if a bytestream request for this user is
diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java
index 8bcfff223..a26d34024 100644
--- a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java
+++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java
@@ -159,9 +159,16 @@ public final class Roster extends Manager {
*/
private final Object rosterListenersAndEntriesLock = new Object();
- // The roster is marked as initialized when at least a single roster packet
- // has been received and processed.
- private boolean loaded = false;
+ private enum RosterState {
+ uninitialized,
+ loading,
+ loaded,
+ }
+
+ /**
+ * The current state of the roster.
+ */
+ private RosterState rosterState = RosterState.uninitialized;
private final PresencePacketListener presencePacketListener = new PresencePacketListener();
@@ -338,9 +345,11 @@ public final class Roster extends Manager {
if (rosterStore != null && isRosterVersioningSupported()) {
packet.setVersion(rosterStore.getRosterVersion());
}
+ rosterState = RosterState.loading;
connection.sendIqWithResponseCallback(packet, new RosterResultListener(), new ExceptionCallback() {
@Override
public void processException(Exception exception) {
+ rosterState = RosterState.uninitialized;
Level logLevel;
if (exception instanceof NotConnectedException) {
logLevel = Level.FINE;
@@ -384,16 +393,15 @@ public final class Roster extends Manager {
return true;
}
- boolean waitUntilLoaded() throws InterruptedException {
- final XMPPConnection connection = connection();
- while (!loaded) {
- long waitTime = connection.getPacketReplyTimeout();
- long start = System.currentTimeMillis();
+ protected boolean waitUntilLoaded() throws InterruptedException {
+ long waitTime = connection().getPacketReplyTimeout();
+ long start = System.currentTimeMillis();
+ while (!isLoaded()) {
if (waitTime <= 0) {
break;
}
synchronized (this) {
- if (!loaded) {
+ if (!isLoaded()) {
wait(waitTime);
}
}
@@ -411,7 +419,7 @@ public final class Roster extends Manager {
* @since 4.1
*/
public boolean isLoaded() {
- return loaded;
+ return rosterState == RosterState.loaded;
}
/**
@@ -1059,7 +1067,7 @@ public final class Roster extends Manager {
}
}
}
- loaded = false;
+ rosterState = RosterState.uninitialized;
}
/**
@@ -1270,6 +1278,21 @@ public final class Roster extends Manager {
@Override
public void processPacket(Stanza packet) throws NotConnectedException, InterruptedException {
+ // Try to ensure that the roster is loaded when processing presence stanzas. While the
+ // presence listener is synchronous, the roster result listener is not, which means that
+ // the presence listener may be invoked with a not yet loaded roster.
+ if (rosterState == RosterState.loading) {
+ try {
+ waitUntilLoaded();
+ }
+ catch (InterruptedException e) {
+ LOGGER.log(Level.INFO, "Presence listener was interrupted", e);
+
+ }
+ }
+ if (!isLoaded()) {
+ LOGGER.warning("Roster not loaded while processing presence stanza");
+ }
Presence presence = (Presence) packet;
Jid from = presence.getFrom();
Resourcepart fromResource = Resourcepart.EMPTY;
@@ -1409,7 +1432,7 @@ public final class Roster extends Manager {
}
}
- loaded = true;
+ rosterState = RosterState.loaded;
synchronized (Roster.this) {
Roster.this.notifyAll();
}