[SMACK-269] - Smack 3.1.0 creates a new chat for every incoming message
[SMACK-271] - Deadlock in XMPPConnection while login and parsing stream features
[SMACK-275] - Patch: Fix for broken SASL DIGEST-MD5 implementation
[SMACK-288] - The parsing of the result for a LeafNode.getItems() call is incorrect. It creates a DefaultPacketExtension instead of an Item for every other item in the result.
@@ -191,6 +192,7 @@ hr {
[SMACK-308] - Multiple errors in pubsub GetItemsRequest
[SMACK-312] - Only fire RosterListener#entriesUpdated for RosterEntries that changed
[SMACK-327] - getFeatures() method on DiscoverInfo is improperly set to be package protected instead of public
+
[SMACK-328] - Number format exception while parsing dates.
+
+When chats are prompted by another user, the setup is slightly different since
+you are receiving a chat message first. Instead of explicitly creating a chat to send
+messages, you need to register to handle newly created Chat instances when the ChatManager
+creates them.
+
+
+The ChatManager will already find a matching chat (by thread id) and if none exists, then it
+will create a new one that does match. To get this new chat, you have to register to be
+notified when it happens. You can register a message listener to receive all future messages as
+part of this handler.
+
+
// Assume we've created a Connection name "connection".
+ChatManager chatmanager = connection.getChatManager().addChatListener(
+ new ChatManagerListener() {
+ @Override
+ public void chatCreated(Chat chat, boolean createdLocally)
+ {
+ if (!createdLocally)
+ chat.addMessageListener(new MyNewMessageListener());;
+ }
+ });
+
+
+In addition to thread based chat messages, there are some clients that
+do not send a thread id as part of the chat. To handle this scenario,
+Smack will attempt match the incoming messages to the best fit existing
+chat, based on the JID. It will attempt to find a chat with the same full
+JID, failing that, it will try the base JID. If no existing chat to the
+user can found, then a new one is created.
diff --git a/source/org/jivesoftware/smack/ChatManager.java b/source/org/jivesoftware/smack/ChatManager.java
index a893f4b4e..1523b4fdc 100644
--- a/source/org/jivesoftware/smack/ChatManager.java
+++ b/source/org/jivesoftware/smack/ChatManager.java
@@ -20,6 +20,13 @@
package org.jivesoftware.smack;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.FromContainsFilter;
import org.jivesoftware.smack.filter.PacketFilter;
@@ -29,9 +36,6 @@ import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.collections.ReferenceMap;
-import java.util.*;
-import java.util.concurrent.CopyOnWriteArraySet;
-
/**
* The chat manager keeps track of references to all current chats. It will not hold any references
* in memory on its own so it is neccesary to keep a reference to the chat object itself. To be
@@ -65,14 +69,20 @@ public class ChatManager {
/**
* Maps thread ID to chat.
*/
- private Map threadChats = new ReferenceMap(ReferenceMap.HARD,
- ReferenceMap.WEAK);
+ private Map threadChats = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.HARD,
+ ReferenceMap.WEAK));
/**
* Maps jids to chats
*/
- private Map jidChats = new ReferenceMap(ReferenceMap.HARD,
- ReferenceMap.WEAK);
+ private Map jidChats = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.HARD,
+ ReferenceMap.WEAK));
+
+ /**
+ * Maps base jids to chats
+ */
+ private Map baseJidChats = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.HARD,
+ ReferenceMap.WEAK));
private Set chatManagerListeners
= new CopyOnWriteArraySet();
@@ -161,6 +171,7 @@ public class ChatManager {
Chat chat = new Chat(this, userJID, threadID);
threadChats.put(threadID, chat);
jidChats.put(userJID, chat);
+ baseJidChats.put(StringUtils.parseBareAddress(userJID), chat);
for(ChatManagerListener listener : chatManagerListeners) {
listener.chatCreated(chat, createdLocally);
@@ -179,8 +190,21 @@ public class ChatManager {
return createChat(userJID, threadID, false);
}
+ /**
+ * Try to get a matching chat for the given user JID. Try the full
+ * JID map first, the try to match on the base JID if no match is
+ * found.
+ *
+ * @param userJID
+ * @return
+ */
private Chat getUserChat(String userJID) {
- return jidChats.get(userJID);
+ Chat match = jidChats.get(userJID);
+
+ if (match == null) {
+ match = baseJidChats.get(StringUtils.parseBareAddress(userJID));
+ }
+ return match;
}
public Chat getThreadChat(String thread) {
diff --git a/source/org/jivesoftware/smack/SmackConfiguration.java b/source/org/jivesoftware/smack/SmackConfiguration.java
index d9faf3b59..d8beb334c 100644
--- a/source/org/jivesoftware/smack/SmackConfiguration.java
+++ b/source/org/jivesoftware/smack/SmackConfiguration.java
@@ -44,7 +44,7 @@ import java.util.*;
*/
public final class SmackConfiguration {
- private static final String SMACK_VERSION = "3.2.0 Beta 2";
+ private static final String SMACK_VERSION = "3.2.0 Beta2";
private static int packetReplyTimeout = 5000;
private static int keepAliveInterval = 30000;
diff --git a/source/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java b/source/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java
index f51c81fdb..86b3673e8 100644
--- a/source/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java
+++ b/source/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java
@@ -46,8 +46,9 @@ public class QueueDetails implements PacketExtension {
*/
public static final String NAMESPACE = "http://jabber.org/protocol/workgroup";
- private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+ private static final String DATE_FORMAT = "yyyyMMdd'T'HH:mm:ss";
+ private SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
/**
* The list of users in the queue.
*/
@@ -124,7 +125,7 @@ public class QueueDetails implements PacketExtension {
if (timestamp != null) {
buf.append("");
- buf.append(DATE_FORMATTER.format(timestamp));
+ buf.append(dateFormat.format(timestamp));
buf.append("");
}
@@ -139,8 +140,10 @@ public class QueueDetails implements PacketExtension {
* Provider class for QueueDetails packet extensions.
*/
public static class Provider implements PacketExtensionProvider {
-
+
public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
QueueDetails queueDetails = new QueueDetails();
int eventType = parser.getEventType();
@@ -163,7 +166,7 @@ public class QueueDetails implements PacketExtension {
eventType = parser.next();
while ((eventType != XmlPullParser.END_TAG)
|| (! "user".equals(parser.getName())))
- {
+ {
if ("position".equals(parser.getName())) {
position = Integer.parseInt(parser.nextText());
}
@@ -171,23 +174,19 @@ public class QueueDetails implements PacketExtension {
time = Integer.parseInt(parser.nextText());
}
else if ("join-time".equals(parser.getName())) {
- joinTime = DATE_FORMATTER.parse(parser.nextText());
+ joinTime = dateFormat.parse(parser.nextText());
}
else if( parser.getName().equals( "waitTime" ) ) {
- Date wait = DATE_FORMATTER.parse( parser.nextText() );
- System.out.println( wait );
+ Date wait = dateFormat.parse(parser.nextText());
+ System.out.println( wait );
}
-
-
-
+
eventType = parser.next();
if (eventType != XmlPullParser.END_TAG) {
// throw exception
}
}
-
-
queueDetails.addUser(new QueueUser(uid, position, time, joinTime));
diff --git a/source/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java b/source/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java
index ef44e9e01..a559579b4 100644
--- a/source/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java
+++ b/source/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java
@@ -39,7 +39,8 @@ public class QueueOverview implements PacketExtension {
*/
public static String NAMESPACE = "http://jabber.org/protocol/workgroup";
- private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
+ private static final String DATE_FORMAT = "yyyyMMdd'T'HH:mm:ss";
+ private SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
private int averageWaitTime;
private Date oldestEntry;
@@ -101,7 +102,7 @@ public class QueueOverview implements PacketExtension {
buf.append("").append(userCount).append("");
}
if (oldestEntry != null) {
- buf.append("").append(DATE_FORMATTER.format(oldestEntry)).append("");
+ buf.append("").append(dateFormat.format(oldestEntry)).append("");
}
if (averageWaitTime != -1) {
buf.append("");
@@ -118,7 +119,8 @@ public class QueueOverview implements PacketExtension {
public PacketExtension parseExtension (XmlPullParser parser) throws Exception {
int eventType = parser.getEventType();
- QueueOverview queueOverview = new QueueOverview();
+ QueueOverview queueOverview = new QueueOverview();
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
if (eventType != XmlPullParser.START_TAG) {
// throw exception
@@ -135,7 +137,7 @@ public class QueueOverview implements PacketExtension {
queueOverview.setAverageWaitTime(Integer.parseInt(parser.nextText()));
}
else if ("oldest".equals(parser.getName())) {
- queueOverview.setOldestEntry((DATE_FORMATTER.parse(parser.nextText())));
+ queueOverview.setOldestEntry((dateFormat.parse(parser.nextText())));
}
else if ("status".equals(parser.getName())) {
queueOverview.setStatus(WorkgroupQueue.Status.fromString(parser.nextText()));
diff --git a/test-unit/org/jivesoftware/smack/ChatConnectionTest.java b/test-unit/org/jivesoftware/smack/ChatConnectionTest.java
new file mode 100644
index 000000000..3ded1ca44
--- /dev/null
+++ b/test-unit/org/jivesoftware/smack/ChatConnectionTest.java
@@ -0,0 +1,288 @@
+/**
+ * $RCSfile$
+ * $Revision: 11640 $
+ * $Date: 2010-02-18 08:38:57 -0500 (Thu, 18 Feb 2010) $
+ *
+ * Copyright 2010 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Tests that verifies the correct behavior of the {@see Roster} implementation.
+ *
+ * @see Roster
+ * @see Roster Management
+ * @author Guenther Niess
+ */
+public class ChatConnectionTest {
+
+ private DummyConnection connection;
+
+ @Before
+ public void setUp() throws Exception {
+ // Uncomment this to enable debug output
+ //Connection.DEBUG_ENABLED = true;
+
+ connection = new DummyConnection();
+ connection.connect();
+ connection.login("me", "secret");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (connection != null)
+ connection.disconnect();
+ }
+
+ /**
+ * Confirm that a new chat is created when a chat message is received but
+ * there is no thread id for a user with only a base jid.
+ */
+ @Test
+ public void chatCreatedWithIncomingChatNoThreadBaseJid()
+ {
+ TestChatManagerListener listener = new TestChatManagerListener();
+ connection.getChatManager().addChatListener(listener);
+
+ Packet incomingChat = createChatPacket(null, false);
+ processServerMessage(incomingChat);
+
+ Chat newChat = listener.getNewChat();
+ assertNotNull(newChat);
+ }
+
+ /**
+ * Confirm that a new chat is created when a chat message is received but
+ * there is no thread id for a user with a full jid.
+ */
+ @Test
+ public void chatCreatedWhenIncomingChatNoThreadFullJid()
+ {
+ TestChatManagerListener listener = new TestChatManagerListener();
+ connection.getChatManager().addChatListener(listener);
+
+ Packet incomingChat = createChatPacket(null, true);
+ processServerMessage(incomingChat);
+
+ Chat newChat = listener.getNewChat();
+ assertNotNull(newChat);
+ }
+
+ /**
+ * Confirm that an existing chat created with a base jid is matched to an
+ * incoming chat message that has no thread id and the user is a full jid.
+ */
+ @Test
+ public void chatFoundWhenNoThreadFullJid()
+ {
+ TestChatManagerListener listener = new TestChatManagerListener();
+ connection.getChatManager().addChatListener(listener);
+ Chat outgoing = connection.getChatManager().createChat("you@testserver", null);
+
+ Packet incomingChat = createChatPacket(null, true);
+ processServerMessage(incomingChat);
+
+ Chat newChat = listener.getNewChat();
+ assertNotNull(newChat);
+ assertTrue(newChat == outgoing);
+ }
+
+ /**
+ * Confirm that an existing chat created with a base jid is matched to an
+ * incoming chat message that has no thread id and the user is a base jid.
+ */
+ @Test
+ public void chatFoundWhenNoThreadBaseJid()
+ {
+ TestChatManagerListener listener = new TestChatManagerListener();
+ connection.getChatManager().addChatListener(listener);
+ Chat outgoing = connection.getChatManager().createChat("you@testserver", null);
+
+ Packet incomingChat = createChatPacket(null, false);
+ processServerMessage(incomingChat);
+
+ Chat newChat = listener.getNewChat();
+ assertNotNull(newChat);
+ assertTrue(newChat == outgoing);
+ }
+
+ /**
+ * Confirm that an existing chat created with a base jid is matched to an
+ * incoming chat message that has the same id and the user is a full jid.
+ */
+ @Test
+ public void chatFoundWithSameThreadFullJid()
+ {
+ TestChatManagerListener listener = new TestChatManagerListener();
+ connection.getChatManager().addChatListener(listener);
+ Chat outgoing = connection.getChatManager().createChat("you@testserver", null);
+
+ Packet incomingChat = createChatPacket(outgoing.getThreadID(), true);
+ processServerMessage(incomingChat);
+
+ Chat newChat = listener.getNewChat();
+ assertNotNull(newChat);
+ assertTrue(newChat == outgoing);
+ }
+
+ /**
+ * Confirm that an existing chat created with a base jid is matched to an
+ * incoming chat message that has the same id and the user is a base jid.
+ */
+ @Test
+ public void chatFoundWithSameThreadBaseJid()
+ {
+ TestChatManagerListener listener = new TestChatManagerListener();
+ connection.getChatManager().addChatListener(listener);
+ Chat outgoing = connection.getChatManager().createChat("you@testserver", null);
+
+ Packet incomingChat = createChatPacket(outgoing.getThreadID(), false);
+ processServerMessage(incomingChat);
+
+ Chat newChat = listener.getNewChat();
+ assertNotNull(newChat);
+ assertTrue(newChat == outgoing);
+ }
+
+ /**
+ * Confirm that an existing chat created with a base jid is not matched to
+ * an incoming chat message that has a different id and the same user as a
+ * base jid.
+ */
+ @Ignore
+ @Test
+ public void chatNotFoundWithDiffThreadBaseJid()
+ {
+ TestChatManagerListener listener = new TestChatManagerListener();
+ connection.getChatManager().addChatListener(listener);
+ Chat outgoing = connection.getChatManager().createChat("you@testserver", null);
+
+ Packet incomingChat = createChatPacket(outgoing.getThreadID() + "ff", false);
+ processServerMessage(incomingChat);
+
+ Chat newChat = listener.getNewChat();
+ assertNotNull(newChat);
+ assertFalse(newChat == outgoing);
+ }
+
+ /**
+ * Confirm that an existing chat created with a base jid is not matched to
+ * an incoming chat message that has a different id and the same base jid.
+ */
+ @Ignore
+ @Test
+ public void chatNotFoundWithDiffThreadFullJid()
+ {
+ TestChatManagerListener listener = new TestChatManagerListener();
+ connection.getChatManager().addChatListener(listener);
+ Chat outgoing = connection.getChatManager().createChat("you@testserver", null);
+
+ Packet incomingChat = createChatPacket(outgoing.getThreadID() + "ff", true);
+ processServerMessage(incomingChat);
+
+ Chat newChat = listener.getNewChat();
+ assertNotNull(newChat);
+ assertFalse(newChat == outgoing);
+ }
+
+ private Packet createChatPacket(final String threadId, final boolean isFullJid)
+ {
+ Message chatMsg = new Message("me@testserver", Message.Type.chat);
+ chatMsg.setBody("the body message");
+ chatMsg.setFrom("you@testserver" + (isFullJid ? "/resource" : ""));
+
+ if (threadId != null)
+ chatMsg.addExtension(new PacketExtension()
+ {
+ @Override
+ public String toXML()
+ {
+ return "" + threadId + "";
+ }
+
+ @Override
+ public String getNamespace()
+ {
+ return null;
+ }
+
+ @Override
+ public String getElementName()
+ {
+ return "thread";
+ }
+ });
+ return chatMsg;
+ }
+
+ private void processServerMessage(Packet incomingChat)
+ {
+ TestChatServer chatServer = new TestChatServer(incomingChat);
+ chatServer.start();
+ try
+ {
+ chatServer.join();
+ } catch (InterruptedException e)
+ {
+ fail();
+ }
+ }
+
+ class TestChatManagerListener implements ChatManagerListener
+ {
+ private Chat newChat;
+
+ @Override
+ public void chatCreated(Chat chat, boolean createdLocally)
+ {
+ newChat = chat;
+ }
+
+ public Chat getNewChat()
+ {
+ return newChat;
+ }
+ }
+
+ private class TestChatServer extends Thread
+ {
+ private Packet chatPacket;
+
+ TestChatServer(Packet chatMsg)
+ {
+ chatPacket = chatMsg;
+ }
+
+ @Override
+ public void run()
+ {
+ connection.processPacket(chatPacket);
+ }
+ }
+}
diff --git a/test/org/jivesoftware/smackx/packet/XHTMLExtensionTest.java b/test/org/jivesoftware/smackx/packet/XHTMLExtensionTest.java
index db6bf10dd..e27238503 100644
--- a/test/org/jivesoftware/smackx/packet/XHTMLExtensionTest.java
+++ b/test/org/jivesoftware/smackx/packet/XHTMLExtensionTest.java
@@ -187,18 +187,18 @@ public class XHTMLExtensionTest extends SmackTestCase {
};
getConnection(1).addPacketListener(packetListener, packetFilter);
- // User1 creates a message to send to user2
- Message msg = new Message();
- msg.setSubject("Any subject you want");
- msg.setBody(
- "awesome! As Emerson once said: A foolish consistency is the hobgoblin of little minds.");
- // Create an XHTMLExtension and add it to the message
- XHTMLExtension xhtmlExtension = new XHTMLExtension();
- xhtmlExtension.addBody(
- "
impresionante!
Como Emerson dijo una vez:
Una consistencia ridicula es el espantajo de mentes pequenas.
");
- xhtmlExtension.addBody(
- "
awesome!
As Emerson once said:
A foolish consistency is the hobgoblin of little minds.
");
- msg.addExtension(xhtmlExtension);
+ // User1 creates a message to send to user2
+ Message msg = new Message();
+ msg.setSubject("Any subject you want");
+ msg.setBody(
+ "awesome! As Emerson once said: A foolish consistency is the hobgoblin of little minds.");
+ // Create an XHTMLExtension and add it to the message
+ XHTMLExtension xhtmlExtension = new XHTMLExtension();
+ xhtmlExtension.addBody(
+ "
impresionante!
Como Emerson dijo una vez:
Una consistencia ridicula es el espantajo de mentes pequenas.
");
+ xhtmlExtension.addBody(
+ "
awesome!
As Emerson once said:
A foolish consistency is the hobgoblin of little minds.
");
+ msg.addExtension(xhtmlExtension);
// User1 sends the message that contains the XHTML to user2
try {