1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-11-22 20:12:07 +01:00

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
This commit is contained in:
rcollier 2011-03-14 01:53:42 +00:00
parent 67a5e6b98d
commit 2150d07435
9 changed files with 387 additions and 38 deletions

View file

@ -28,7 +28,7 @@
<property name="version.major" value="3" /> <property name="version.major" value="3" />
<property name="version.minor" value="2" /> <property name="version.minor" value="2" />
<property name="version.revision" value="0" /> <property name="version.revision" value="0" />
<property name="version.extra" value="Beta" /> <property name="version.extra" value="Beta2" />
<if> <if>
<equals arg1="${version.extra}" arg2=""/> <equals arg1="${version.extra}" arg2=""/>

View file

@ -181,6 +181,7 @@ hr {
<li>[<a href='http://issues.igniterealtime.org/browse/SMACK-232'>SMACK-232</a>] - Better handling of Roster error</li> <li>[<a href='http://issues.igniterealtime.org/browse/SMACK-232'>SMACK-232</a>] - Better handling of Roster error</li>
<li>[<a href='http://issues.igniterealtime.org/browse/SMACK-243'>SMACK-243</a>] - Packet with wrong date format makes Smack to disconnect</li> <li>[<a href='http://issues.igniterealtime.org/browse/SMACK-243'>SMACK-243</a>] - Packet with wrong date format makes Smack to disconnect</li>
<li>[<a href='http://issues.igniterealtime.org/browse/SMACK-264'>SMACK-264</a>] - fix for NPE in SASLMechanism.java</li> <li>[<a href='http://issues.igniterealtime.org/browse/SMACK-264'>SMACK-264</a>] - fix for NPE in SASLMechanism.java</li>
<li>[<a href='http://issues.igniterealtime.org/browse/SMACK-269'>SMACK-269</a>] - Smack 3.1.0 creates a new chat for every incoming message</li>
<li>[<a href='http://issues.igniterealtime.org/browse/SMACK-271'>SMACK-271</a>] - Deadlock in XMPPConnection while login and parsing stream features</li> <li>[<a href='http://issues.igniterealtime.org/browse/SMACK-271'>SMACK-271</a>] - Deadlock in XMPPConnection while login and parsing stream features</li>
<li>[<a href='http://issues.igniterealtime.org/browse/SMACK-275'>SMACK-275</a>] - Patch: Fix for broken SASL DIGEST-MD5 implementation</li> <li>[<a href='http://issues.igniterealtime.org/browse/SMACK-275'>SMACK-275</a>] - Patch: Fix for broken SASL DIGEST-MD5 implementation</li>
<li>[<a href='http://issues.igniterealtime.org/browse/SMACK-288'>SMACK-288</a>] - 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.</li> <li>[<a href='http://issues.igniterealtime.org/browse/SMACK-288'>SMACK-288</a>] - 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.</li>
@ -191,6 +192,7 @@ hr {
<li>[<a href='http://issues.igniterealtime.org/browse/SMACK-308'>SMACK-308</a>] - Multiple errors in pubsub GetItemsRequest</li> <li>[<a href='http://issues.igniterealtime.org/browse/SMACK-308'>SMACK-308</a>] - Multiple errors in pubsub GetItemsRequest</li>
<li>[<a href='http://issues.igniterealtime.org/browse/SMACK-312'>SMACK-312</a>] - Only fire RosterListener#entriesUpdated for RosterEntries that changed</li> <li>[<a href='http://issues.igniterealtime.org/browse/SMACK-312'>SMACK-312</a>] - Only fire RosterListener#entriesUpdated for RosterEntries that changed</li>
<li>[<a href='http://issues.igniterealtime.org/browse/SMACK-327'>SMACK-327</a>] - getFeatures() method on DiscoverInfo is improperly set to be package protected instead of public</li> <li>[<a href='http://issues.igniterealtime.org/browse/SMACK-327'>SMACK-327</a>] - getFeatures() method on DiscoverInfo is improperly set to be package protected instead of public</li>
<li>[<a href='http://issues.igniterealtime.org/browse/SMACK-328'>SMACK-328</a>] - Number format exception while parsing dates.</li>
</ul> </ul>
<h2>3.1.0 -- <span style="font-weight: normal;">November 20, 2008</span></h2> <h2>3.1.0 -- <span style="font-weight: normal;">November 20, 2008</span></h2>

View file

@ -82,6 +82,40 @@ newChat.sendMessage(newMessage);
} }
</pre> </pre>
</div> </div>
<p class="subheader">
Incoming Chat
</p>
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.
</br>
</br>
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.<p>
<div class="code"><pre><font color="gray"><i>// Assume we've created a Connection name "connection".</i></font>
ChatManager chatmanager = connection.getChatManager().addChatListener(
new ChatManagerListener() {
@Override
public void chatCreated(Chat chat, boolean createdLocally)
{
if (!createdLocally)
chat.addMessageListener(new MyNewMessageListener());;
}
});
</pre>
</div>
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.
<p> <p>
<br clear="all"/><br><br> <br clear="all"/><br><br>

View file

@ -20,6 +20,13 @@
package org.jivesoftware.smack; 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.AndFilter;
import org.jivesoftware.smack.filter.FromContainsFilter; import org.jivesoftware.smack.filter.FromContainsFilter;
import org.jivesoftware.smack.filter.PacketFilter; 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.StringUtils;
import org.jivesoftware.smack.util.collections.ReferenceMap; 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 * 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 * 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. * Maps thread ID to chat.
*/ */
private Map<String, Chat> threadChats = new ReferenceMap<String, Chat>(ReferenceMap.HARD, private Map<String, Chat> threadChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
ReferenceMap.WEAK); ReferenceMap.WEAK));
/** /**
* Maps jids to chats * Maps jids to chats
*/ */
private Map<String, Chat> jidChats = new ReferenceMap<String, Chat>(ReferenceMap.HARD, private Map<String, Chat> jidChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
ReferenceMap.WEAK); ReferenceMap.WEAK));
/**
* Maps base jids to chats
*/
private Map<String, Chat> baseJidChats = Collections.synchronizedMap(new ReferenceMap<String, Chat>(ReferenceMap.HARD,
ReferenceMap.WEAK));
private Set<ChatManagerListener> chatManagerListeners private Set<ChatManagerListener> chatManagerListeners
= new CopyOnWriteArraySet<ChatManagerListener>(); = new CopyOnWriteArraySet<ChatManagerListener>();
@ -161,6 +171,7 @@ public class ChatManager {
Chat chat = new Chat(this, userJID, threadID); Chat chat = new Chat(this, userJID, threadID);
threadChats.put(threadID, chat); threadChats.put(threadID, chat);
jidChats.put(userJID, chat); jidChats.put(userJID, chat);
baseJidChats.put(StringUtils.parseBareAddress(userJID), chat);
for(ChatManagerListener listener : chatManagerListeners) { for(ChatManagerListener listener : chatManagerListeners) {
listener.chatCreated(chat, createdLocally); listener.chatCreated(chat, createdLocally);
@ -179,8 +190,21 @@ public class ChatManager {
return createChat(userJID, threadID, false); 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) { 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) { public Chat getThreadChat(String thread) {

View file

@ -44,7 +44,7 @@ import java.util.*;
*/ */
public final class SmackConfiguration { 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 packetReplyTimeout = 5000;
private static int keepAliveInterval = 30000; private static int keepAliveInterval = 30000;

View file

@ -46,8 +46,9 @@ public class QueueDetails implements PacketExtension {
*/ */
public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; 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. * The list of users in the queue.
*/ */
@ -124,7 +125,7 @@ public class QueueDetails implements PacketExtension {
if (timestamp != null) { if (timestamp != null) {
buf.append("<join-time>"); buf.append("<join-time>");
buf.append(DATE_FORMATTER.format(timestamp)); buf.append(dateFormat.format(timestamp));
buf.append("</join-time>"); buf.append("</join-time>");
} }
@ -141,6 +142,8 @@ public class QueueDetails implements PacketExtension {
public static class Provider implements PacketExtensionProvider { public static class Provider implements PacketExtensionProvider {
public PacketExtension parseExtension(XmlPullParser parser) throws Exception { public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
QueueDetails queueDetails = new QueueDetails(); QueueDetails queueDetails = new QueueDetails();
int eventType = parser.getEventType(); int eventType = parser.getEventType();
@ -171,15 +174,13 @@ public class QueueDetails implements PacketExtension {
time = Integer.parseInt(parser.nextText()); time = Integer.parseInt(parser.nextText());
} }
else if ("join-time".equals(parser.getName())) { else if ("join-time".equals(parser.getName())) {
joinTime = DATE_FORMATTER.parse(parser.nextText()); joinTime = dateFormat.parse(parser.nextText());
} }
else if( parser.getName().equals( "waitTime" ) ) { else if( parser.getName().equals( "waitTime" ) ) {
Date wait = DATE_FORMATTER.parse( parser.nextText() ); Date wait = dateFormat.parse(parser.nextText());
System.out.println( wait ); System.out.println( wait );
} }
eventType = parser.next(); eventType = parser.next();
if (eventType != XmlPullParser.END_TAG) { if (eventType != XmlPullParser.END_TAG) {
@ -187,8 +188,6 @@ public class QueueDetails implements PacketExtension {
} }
} }
queueDetails.addUser(new QueueUser(uid, position, time, joinTime)); queueDetails.addUser(new QueueUser(uid, position, time, joinTime));
eventType = parser.next(); eventType = parser.next();

View file

@ -39,7 +39,8 @@ public class QueueOverview implements PacketExtension {
*/ */
public static String NAMESPACE = "http://jabber.org/protocol/workgroup"; 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 int averageWaitTime;
private Date oldestEntry; private Date oldestEntry;
@ -101,7 +102,7 @@ public class QueueOverview implements PacketExtension {
buf.append("<count>").append(userCount).append("</count>"); buf.append("<count>").append(userCount).append("</count>");
} }
if (oldestEntry != null) { if (oldestEntry != null) {
buf.append("<oldest>").append(DATE_FORMATTER.format(oldestEntry)).append("</oldest>"); buf.append("<oldest>").append(dateFormat.format(oldestEntry)).append("</oldest>");
} }
if (averageWaitTime != -1) { if (averageWaitTime != -1) {
buf.append("<time>").append(averageWaitTime).append("</time>"); buf.append("<time>").append(averageWaitTime).append("</time>");
@ -119,6 +120,7 @@ public class QueueOverview implements PacketExtension {
public PacketExtension parseExtension (XmlPullParser parser) throws Exception { public PacketExtension parseExtension (XmlPullParser parser) throws Exception {
int eventType = parser.getEventType(); int eventType = parser.getEventType();
QueueOverview queueOverview = new QueueOverview(); QueueOverview queueOverview = new QueueOverview();
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
if (eventType != XmlPullParser.START_TAG) { if (eventType != XmlPullParser.START_TAG) {
// throw exception // throw exception
@ -135,7 +137,7 @@ public class QueueOverview implements PacketExtension {
queueOverview.setAverageWaitTime(Integer.parseInt(parser.nextText())); queueOverview.setAverageWaitTime(Integer.parseInt(parser.nextText()));
} }
else if ("oldest".equals(parser.getName())) { 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())) { else if ("status".equals(parser.getName())) {
queueOverview.setStatus(WorkgroupQueue.Status.fromString(parser.nextText())); queueOverview.setStatus(WorkgroupQueue.Status.fromString(parser.nextText()));

View file

@ -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 <a href="http://xmpp.org/rfcs/rfc3921.html#roster">Roster Management</a>
* @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 "<thread>" + threadId + "</thread>";
}
@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);
}
}
}

View file

@ -187,18 +187,18 @@ public class XHTMLExtensionTest extends SmackTestCase {
}; };
getConnection(1).addPacketListener(packetListener, packetFilter); getConnection(1).addPacketListener(packetListener, packetFilter);
// User1 creates a message to send to user2 // User1 creates a message to send to user2
Message msg = new Message(); Message msg = new Message();
msg.setSubject("Any subject you want"); msg.setSubject("Any subject you want");
msg.setBody( msg.setBody(
"awesome! As Emerson once said: A foolish consistency is the hobgoblin of little minds."); "awesome! As Emerson once said: A foolish consistency is the hobgoblin of little minds.");
// Create an XHTMLExtension and add it to the message // Create an XHTMLExtension and add it to the message
XHTMLExtension xhtmlExtension = new XHTMLExtension(); XHTMLExtension xhtmlExtension = new XHTMLExtension();
xhtmlExtension.addBody( xhtmlExtension.addBody(
"<body xml:lang=\"es-ES\"><h1>impresionante!</h1><p>Como Emerson dijo una vez:</p><blockquote><p>Una consistencia ridicula es el espantajo de mentes pequenas.</p></blockquote></body>"); "<body xml:lang=\"es-ES\"><h1>impresionante!</h1><p>Como Emerson dijo una vez:</p><blockquote><p>Una consistencia ridicula es el espantajo de mentes pequenas.</p></blockquote></body>");
xhtmlExtension.addBody( xhtmlExtension.addBody(
"<body xml:lang=\"en-US\"><h1>awesome!</h1><p>As Emerson once said:</p><blockquote><p>A foolish consistency is the hobgoblin of little minds.</p></blockquote></body>"); "<body xml:lang=\"en-US\"><h1>awesome!</h1><p>As Emerson once said:</p><blockquote><p>A foolish consistency is the hobgoblin of little minds.</p></blockquote></body>");
msg.addExtension(xhtmlExtension); msg.addExtension(xhtmlExtension);
// User1 sends the message that contains the XHTML to user2 // User1 sends the message that contains the XHTML to user2
try { try {