From be879863bea575d35a9811bc0e3cfb549b0e3dc1 Mon Sep 17 00:00:00 2001 From: Gaston Dombiak Date: Tue, 26 Jul 2005 04:57:36 +0000 Subject: [PATCH] Initial version. SMACK-25 git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@2512 b35dd754-fafc-0310-a699-88a17e54d16e --- .../smackx/OfflineMessageHeader.java | 85 ++++++ .../smackx/OfflineMessageManager.java | 284 ++++++++++++++++++ .../smackx/packet/OfflineMessageInfo.java | 128 ++++++++ .../smackx/packet/OfflineMessageRequest.java | 237 +++++++++++++++ .../smackx/OfflineMessageManagerTest.java | 184 ++++++++++++ 5 files changed, 918 insertions(+) create mode 100644 source/org/jivesoftware/smackx/OfflineMessageHeader.java create mode 100644 source/org/jivesoftware/smackx/OfflineMessageManager.java create mode 100644 source/org/jivesoftware/smackx/packet/OfflineMessageInfo.java create mode 100644 source/org/jivesoftware/smackx/packet/OfflineMessageRequest.java create mode 100644 test/org/jivesoftware/smackx/OfflineMessageManagerTest.java diff --git a/source/org/jivesoftware/smackx/OfflineMessageHeader.java b/source/org/jivesoftware/smackx/OfflineMessageHeader.java new file mode 100644 index 000000000..c3ff215a1 --- /dev/null +++ b/source/org/jivesoftware/smackx/OfflineMessageHeader.java @@ -0,0 +1,85 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 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.smackx; + +import org.jivesoftware.smackx.packet.DiscoverItems; + +/** + * The OfflineMessageHeader holds header information of an offline message. The header + * information was retrieved using the {@link OfflineMessageManager} class.

+ * + * Each offline message is identified by the target user of the offline message and a unique stamp. + * Use {@link OfflineMessageManager#getMessages(java.util.List)} to retrieve the whole message. + * + * @author Gaston Dombiak + */ +public class OfflineMessageHeader { + /** + * Bare JID of the user that was offline when the message was sent. + */ + private String user; + /** + * Full JID of the user that sent the message. + */ + private String jid; + /** + * Stamp that uniquely identifies the offline message. This stamp will be used for + * getting the specific message or delete it. The stamp may be of the form UTC timestamps + * but it is not required to have that format. + */ + private String stamp; + + public OfflineMessageHeader(DiscoverItems.Item item) { + super(); + user = item.getEntityID(); + jid = item.getName(); + stamp = item.getNode(); + } + + /** + * Returns the bare JID of the user that was offline when the message was sent. + * + * @return the bare JID of the user that was offline when the message was sent. + */ + public String getUser() { + return user; + } + + /** + * Returns the full JID of the user that sent the message. + * + * @return the full JID of the user that sent the message. + */ + public String getJid() { + return jid; + } + + /** + * Returns the stamp that uniquely identifies the offline message. This stamp will + * be used for getting the specific message or delete it. The stamp may be of the + * form UTC timestamps but it is not required to have that format. + * + * @return the stamp that uniquely identifies the offline message. + */ + public String getStamp() { + return stamp; + } +} diff --git a/source/org/jivesoftware/smackx/OfflineMessageManager.java b/source/org/jivesoftware/smackx/OfflineMessageManager.java new file mode 100644 index 000000000..ba4331d83 --- /dev/null +++ b/source/org/jivesoftware/smackx/OfflineMessageManager.java @@ -0,0 +1,284 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 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.smackx; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.smackx.packet.OfflineMessageInfo; +import org.jivesoftware.smackx.packet.OfflineMessageRequest; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * The OfflineMessageManager helps manage offline messages even before the user has sent an + * available presence. When a user asks for his offline messages before sending an available + * presence then the server will not send a flood with all the offline messages when the user + * becomes online. The server will not send a flood with all the offline messages to the session + * that made the offline messages request or to any other session used by the user that becomes + * online.

+ * + * Once the session that made the offline messages request has been closed and the user becomes + * offline in all the resources then the server will resume storing the messages offline and will + * send all the offline messages to the user when he becomes online. Therefore, the server will + * flood the user when he becomes online unless the user uses this class to manage his offline + * messages. + * + * @author Gaston Dombiak + */ +public class OfflineMessageManager { + + private final static String namespace = "http://jabber.org/protocol/offline"; + + private XMPPConnection connection; + + private PacketFilter packetFilter; + + public OfflineMessageManager(XMPPConnection connection) { + this.connection = connection; + packetFilter = + new AndFilter(new PacketExtensionFilter("offline", namespace), + new PacketTypeFilter(Message.class)); + } + + /** + * Returns true if the server supports Flexible Offline Message Retrieval. When the server + * supports Flexible Offline Message Retrieval it is possible to get the header of the offline + * messages, get specific messages, delete specific messages, etc. + * + * @return a boolean indicating if the server supports Flexible Offline Message Retrieval. + * @throws XMPPException If the user is not allowed to make this request. + */ + public boolean supportsFlexibleRetrieval() throws XMPPException { + DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null); + return info.containsFeature(namespace); + } + + /** + * Returns the number of offline messages for the user of the connection. + * + * @return the number of offline messages for the user of the connection. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public int getMessageCount() throws XMPPException { + DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null, + namespace); + Form extendedInfo = Form.getFormFrom(info); + if (extendedInfo != null) { + String value = (String) extendedInfo.getField("number_of_messages").getValues().next(); + return Integer.parseInt(value); + } + return 0; + } + + /** + * Returns an iterator on OfflineMessageHeader that keep information about the + * offline message. The OfflineMessageHeader includes a stamp that could be used to retrieve + * the complete message or delete the specific message. + * + * @return an iterator on OfflineMessageHeader that keep information about the offline + * message. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public Iterator getHeaders() throws XMPPException { + List answer = new ArrayList(); + DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection).discoverItems( + null, namespace); + for (Iterator it = items.getItems(); it.hasNext();) { + DiscoverItems.Item item = (DiscoverItems.Item) it.next(); + answer.add(new OfflineMessageHeader(item)); + } + return answer.iterator(); + } + + /** + * Returns an Iterator with the offline Messages whose stamp matches the specified + * request. The request will include the list of stamps that uniquely identifies + * the offline messages to retrieve. The returned offline messages will not be deleted + * from the server. Use {@link #deleteMessages(java.util.List)} to delete the messages. + * + * @param nodes the list of stamps that uniquely identifies offline message. + * @return an Iterator with the offline Messages that were received as part of + * this request. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public Iterator getMessages(final List nodes) throws XMPPException { + List messages = new ArrayList(); + OfflineMessageRequest request = new OfflineMessageRequest(); + for (Iterator it = nodes.iterator(); it.hasNext();) { + OfflineMessageRequest.Item item = new OfflineMessageRequest.Item((String) it.next()); + item.setAction("view"); + request.addItem(item); + } + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Filter offline messages that were requested by this request + PacketFilter messageFilter = new AndFilter(packetFilter, new PacketFilter() { + public boolean accept(Packet packet) { + OfflineMessageInfo info = (OfflineMessageInfo) packet.getExtension("offline", + namespace); + return nodes.contains(info.getNode()); + } + }); + PacketCollector messageCollector = connection.createPacketCollector(messageFilter); + // Send the retrieval request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + + // Collect the received offline messages + Message message = (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + while (message != null) { + messages.add(message); + message = + (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + } + // Stop queuing offline messages + messageCollector.cancel(); + return messages.iterator(); + } + + /** + * Returns an Iterator with all the offline Messages of the user. The returned offline + * messages will not be deleted from the server. Use {@link #deleteMessages(java.util.List)} + * to delete the messages. + * + * @return an Iterator with all the offline Messages of the user. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public Iterator getMessages() throws XMPPException { + List messages = new ArrayList(); + OfflineMessageRequest request = new OfflineMessageRequest(); + request.setFetch(true); + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Filter offline messages that were requested by this request + PacketCollector messageCollector = connection.createPacketCollector(packetFilter); + // Send the retrieval request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + + // Collect the received offline messages + Message message = (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + while (message != null) { + messages.add(message); + message = + (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + } + // Stop queuing offline messages + messageCollector.cancel(); + return messages.iterator(); + } + + /** + * Deletes the specified list of offline messages. The request will include the list of + * stamps that uniquely identifies the offline messages to delete. + * + * @param nodes the list of stamps that uniquely identifies offline message. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public void deleteMessages(List nodes) throws XMPPException { + OfflineMessageRequest request = new OfflineMessageRequest(); + for (Iterator it = nodes.iterator(); it.hasNext();) { + OfflineMessageRequest.Item item = new OfflineMessageRequest.Item((String) it.next()); + item.setAction("remove"); + request.addItem(item); + } + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the deletion request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + /** + * Deletes all offline messages of the user. + * + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public void deleteMessages() throws XMPPException { + OfflineMessageRequest request = new OfflineMessageRequest(); + request.setPurge(true); + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the deletion request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } +} diff --git a/source/org/jivesoftware/smackx/packet/OfflineMessageInfo.java b/source/org/jivesoftware/smackx/packet/OfflineMessageInfo.java new file mode 100644 index 000000000..90c1ead71 --- /dev/null +++ b/source/org/jivesoftware/smackx/packet/OfflineMessageInfo.java @@ -0,0 +1,128 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 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.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * OfflineMessageInfo is an extension included in the retrieved offline messages requested by + * the {@link org.jivesoftware.smackx.OfflineMessageManager}. This extension includes a stamp + * that uniquely identifies the offline message. This stamp may be used for deleting the offline + * message. The stamp may be of the form UTC timestamps but it is not required to have that format. + * + * @author Gaston Dombiak + */ +public class OfflineMessageInfo implements PacketExtension { + + private String node = null; + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "offline" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "offline"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "http://jabber.org/protocol/offline" + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "http://jabber.org/protocol/offline"; + } + + /** + * Returns the stamp that uniquely identifies the offline message. This stamp may + * be used for deleting the offline message. The stamp may be of the form UTC timestamps + * but it is not required to have that format. + * + * @return the stamp that uniquely identifies the offline message. + */ + public String getNode() { + return node; + } + + /** + * Sets the stamp that uniquely identifies the offline message. This stamp may + * be used for deleting the offline message. The stamp may be of the form UTC timestamps + * but it is not required to have that format. + * + * @param node the stamp that uniquely identifies the offline message. + */ + public void setNode(String node) { + this.node = node; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + if (getNode() != null) + buf.append(""); + buf.append(""); + return buf.toString(); + } + + public static class Provider implements PacketExtensionProvider { + + /** + * Creates a new Provider. + * ProviderManager requires that every PacketExtensionProvider has a public, + * no-argument constructor + */ + public Provider() { + } + + /** + * Parses a OfflineMessageInfo packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception { + OfflineMessageInfo info = new OfflineMessageInfo(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) + info.setNode(parser.getAttributeValue("", "node")); + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("offline")) { + done = true; + } + } + } + + return info; + } + + } +} diff --git a/source/org/jivesoftware/smackx/packet/OfflineMessageRequest.java b/source/org/jivesoftware/smackx/packet/OfflineMessageRequest.java new file mode 100644 index 000000000..9b726e3c6 --- /dev/null +++ b/source/org/jivesoftware/smackx/packet/OfflineMessageRequest.java @@ -0,0 +1,237 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 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.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Represents a request to get some or all the offline messages of a user. This class can also + * be used for deleting some or all the offline messages of a user. + * + * @author Gaston Dombiak + */ +public class OfflineMessageRequest extends IQ { + + private List items = new ArrayList(); + private boolean purge = false; + private boolean fetch = false; + + /** + * Returns an Iterator for item childs that holds information about offline messages to + * view or delete. + * + * @return an Iterator for item childs that holds information about offline messages to + * view or delete. + */ + public Iterator getItems() { + synchronized (items) { + return Collections.unmodifiableList(new ArrayList(items)).iterator(); + } + } + + /** + * Adds an item child that holds information about offline messages to view or delete. + * + * @param item the item child that holds information about offline messages to view or delete. + */ + public void addItem(Item item) { + synchronized (items) { + items.add(item); + } + } + + /** + * Returns true if all the offline messages of the user should be deleted. + * + * @return true if all the offline messages of the user should be deleted. + */ + public boolean isPurge() { + return purge; + } + + /** + * Sets if all the offline messages of the user should be deleted. + * + * @param purge true if all the offline messages of the user should be deleted. + */ + public void setPurge(boolean purge) { + this.purge = purge; + } + + /** + * Returns true if all the offline messages of the user should be retrieved. + * + * @return true if all the offline messages of the user should be retrieved. + */ + public boolean isFetch() { + return fetch; + } + + /** + * Sets if all the offline messages of the user should be retrieved. + * + * @param fetch true if all the offline messages of the user should be retrieved. + */ + public void setFetch(boolean fetch) { + this.fetch = fetch; + } + + public String getChildElementXML() { + StringBuffer buf = new StringBuffer(); + buf.append(""); + synchronized (items) { + for (int i = 0; i < items.size(); i++) { + Item item = (Item) items.get(i); + buf.append(item.toXML()); + } + } + if (purge) { + buf.append(""); + } + if (fetch) { + buf.append(""); + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append(""); + return buf.toString(); + } + + /** + * Item child that holds information about offline messages to view or delete. + * + * @author Gaston Dombiak + */ + public static class Item { + private String action; + private String jid; + private String node; + + /** + * Creates a new item child. + * + * @param node the actor's affiliation to the room + */ + public Item(String node) { + this.node = node; + } + + public String getNode() { + return node; + } + + /** + * Returns "view" or "remove" that indicate if the server should return the specified + * offline message or delete it. + * + * @return "view" or "remove" that indicate if the server should return the specified + * offline message or delete it. + */ + public String getAction() { + return action; + } + + /** + * Sets if the server should return the specified offline message or delete it. Possible + * values are "view" or "remove". + * + * @param action if the server should return the specified offline message or delete it. + */ + public void setAction(String action) { + this.action = action; + } + + public String getJid() { + return jid; + } + + public void setJid(String jid) { + this.jid = jid; + } + + public String toXML() { + StringBuffer buf = new StringBuffer(); + buf.append(""); + return buf.toString(); + } + } + + public static class Provider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + OfflineMessageRequest request = new OfflineMessageRequest(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + request.addItem(parseItem(parser)); + } + else if (parser.getName().equals("purge")) { + request.setPurge(true); + } + else if (parser.getName().equals("fetch")) { + request.setFetch(true); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("offline")) { + done = true; + } + } + } + + return request; + } + + private Item parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + Item item = new Item(parser.getAttributeValue("", "node")); + item.setAction(parser.getAttributeValue("", "action")); + item.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return item; + } + } +} diff --git a/test/org/jivesoftware/smackx/OfflineMessageManagerTest.java b/test/org/jivesoftware/smackx/OfflineMessageManagerTest.java new file mode 100644 index 000000000..07c2fae6d --- /dev/null +++ b/test/org/jivesoftware/smackx/OfflineMessageManagerTest.java @@ -0,0 +1,184 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2004 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.smackx; + +import org.jivesoftware.smack.Chat; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.MessageTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smackx.packet.OfflineMessageInfo; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Tests handling of offline messaging using OfflineMessageManager. This server requires the + * server to support JEP-0013: Flexible Offline Message Retrieval. + * + * @author Gaston Dombiak + */ +public class OfflineMessageManagerTest extends SmackTestCase { + + public OfflineMessageManagerTest(String arg0) { + super(arg0); + } + + public void testDiscoverFlexibleRetrievalSupport() throws XMPPException { + OfflineMessageManager offlineManager = new OfflineMessageManager(getConnection(1)); + assertTrue("Server does not support JEP-13", offlineManager.supportsFlexibleRetrieval()); + } + + /** + * While user2 is connected but unavailable, user1 sends 2 messages to user1. User2 then + * performs some "Flexible Offline Message Retrieval" checking the number of offline messages, + * retriving the headers, then the real messages of the headers and finally removing the + * loaded messages. + */ + public void testReadAndDelete() { + // Make user2 unavailable + getConnection(1).sendPacket(new Presence(Presence.Type.UNAVAILABLE)); + + try { + Thread.sleep(500); + + // User1 sends some messages to User2 which is not available at the moment + Chat chat = getConnection(0).createChat(getBareJID(1)); + chat.sendMessage("Test 1"); + chat.sendMessage("Test 2"); + + Thread.sleep(500); + + // User2 checks the number of offline messages + OfflineMessageManager offlineManager = new OfflineMessageManager(getConnection(1)); + assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount()); + // Check the message headers + Iterator headers = offlineManager.getHeaders(); + assertTrue("No message header was found", headers.hasNext()); + List stamps = new ArrayList(); + while (headers.hasNext()) { + OfflineMessageHeader header = (OfflineMessageHeader) headers.next(); + assertEquals("Incorrect sender", getFullJID(0), header.getJid()); + assertNotNull("No stamp was found in the message header", header.getStamp()); + stamps.add(header.getStamp()); + } + assertEquals("Wrong number of headers", 2, stamps.size()); + // Get the offline messages + Iterator messages = offlineManager.getMessages(stamps); + assertTrue("No message was found", messages.hasNext()); + stamps = new ArrayList(); + while (messages.hasNext()) { + Message message = (Message) messages.next(); + OfflineMessageInfo info = (OfflineMessageInfo) message.getExtension("offline", + "http://jabber.org/protocol/offline"); + assertNotNull("No offline information was included in the offline message", info); + assertNotNull("No stamp was found in the message header", info.getNode()); + stamps.add(info.getNode()); + } + assertEquals("Wrong number of messages", 2, stamps.size()); + // Check that the offline messages have not been deleted + assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount()); + + // User2 becomes available again + PacketCollector collector = getConnection(1).createPacketCollector( + new MessageTypeFilter(Message.Type.CHAT)); + getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE)); + + // Check that no offline messages was sent to the user + Message message = (Message) collector.nextResult(2500); + assertNull("An offline message was sent from the server", message); + + // Delete the retrieved offline messages + offlineManager.deleteMessages(stamps); + // Check that there are no offline message for this user + assertEquals("Wrong number of offline messages", 0, offlineManager.getMessageCount()); + + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + /** + * While user2 is connected but unavailable, user1 sends 2 messages to user1. User2 then + * performs some "Flexible Offline Message Retrieval" by fetching all the offline messages + * and then removing all the offline messages. + */ + public void testFetchAndPurge() { + // Make user2 unavailable + getConnection(1).sendPacket(new Presence(Presence.Type.UNAVAILABLE)); + + try { + Thread.sleep(500); + + // User1 sends some messages to User2 which is not available at the moment + Chat chat = getConnection(0).createChat(getBareJID(1)); + chat.sendMessage("Test 1"); + chat.sendMessage("Test 2"); + + Thread.sleep(500); + + // User2 checks the number of offline messages + OfflineMessageManager offlineManager = new OfflineMessageManager(getConnection(1)); + assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount()); + // Get all offline messages + Iterator messages = offlineManager.getMessages(); + assertTrue("No message was found", messages.hasNext()); + List stamps = new ArrayList(); + while (messages.hasNext()) { + Message message = (Message) messages.next(); + OfflineMessageInfo info = (OfflineMessageInfo) message.getExtension("offline", + "http://jabber.org/protocol/offline"); + assertNotNull("No offline information was included in the offline message", info); + assertNotNull("No stamp was found in the message header", info.getNode()); + stamps.add(info.getNode()); + } + assertEquals("Wrong number of messages", 2, stamps.size()); + // Check that the offline messages have not been deleted + assertEquals("Wrong number of offline messages", 2, offlineManager.getMessageCount()); + + // User2 becomes available again + PacketCollector collector = getConnection(1).createPacketCollector( + new MessageTypeFilter(Message.Type.CHAT)); + getConnection(1).sendPacket(new Presence(Presence.Type.AVAILABLE)); + + // Check that no offline messages was sent to the user + Message message = (Message) collector.nextResult(2500); + assertNull("An offline message was sent from the server", message); + + // Delete all offline messages + offlineManager.deleteMessages(); + // Check that there are no offline message for this user + assertEquals("Wrong number of offline messages", 0, offlineManager.getMessageCount()); + + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + protected int getMaxConnections() { + return 2; + } +}