From 2150d07435476e6b6d9bffe6e96c4ae168af6dd8 Mon Sep 17 00:00:00 2001 From: rcollier Date: Mon, 14 Mar 2011 01:53:42 +0000 Subject: [PATCH] Merged the 3.2 Beta branch into trunk. git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@12107 b35dd754-fafc-0310-a699-88a17e54d16e --- build/build.xml | 2 +- build/resources/releasedocs/changelog.html | 2 + documentation/messaging.html | 34 +++ .../org/jivesoftware/smack/ChatManager.java | 40 ++- .../smack/SmackConfiguration.java | 2 +- .../smackx/workgroup/packet/QueueDetails.java | 23 +- .../workgroup/packet/QueueOverview.java | 10 +- .../smack/ChatConnectionTest.java | 288 ++++++++++++++++++ .../smackx/packet/XHTMLExtensionTest.java | 24 +- 9 files changed, 387 insertions(+), 38 deletions(-) create mode 100644 test-unit/org/jivesoftware/smack/ChatConnectionTest.java diff --git a/build/build.xml b/build/build.xml index d4e7a98a9..c30efd8cb 100644 --- a/build/build.xml +++ b/build/build.xml @@ -28,7 +28,7 @@ - + diff --git a/build/resources/releasedocs/changelog.html b/build/resources/releasedocs/changelog.html index 204e6bcb4..b2d91cf82 100644 --- a/build/resources/releasedocs/changelog.html +++ b/build/resources/releasedocs/changelog.html @@ -181,6 +181,7 @@ hr {
  • [SMACK-232] - Better handling of Roster error
  • [SMACK-243] - Packet with wrong date format makes Smack to disconnect
  • [SMACK-264] - fix for NPE in SASLMechanism.java
  • +
  • [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.
  • 3.1.0 -- November 20, 2008

    diff --git a/documentation/messaging.html b/documentation/messaging.html index 35af39c74..457c8d6a7 100644 --- a/documentation/messaging.html +++ b/documentation/messaging.html @@ -82,6 +82,40 @@ newChat.sendMessage(newMessage); } + +

    + Incoming Chat +

    + +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 {