/** * $RCSfile$ * $Revision: 11613 $ * $Date: 2010-02-09 05:55:56 -0600 (Tue, 09 Feb 2010) $ * * Copyright 2003-2007 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 java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.jivesoftware.smack.Connection; import org.jivesoftware.smack.PacketCollector; import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.PacketExtensionFilter; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.filter.PacketIDFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; 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.DiscoverItems.Item; import org.jivesoftware.smackx.packet.OfflineMessageInfo; import org.jivesoftware.smackx.packet.OfflineMessageRequest; /** * 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. * <p> * * 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 final Connection connection; private final PacketFilter packetFilter; public OfflineMessageManager(Connection connection) { this.connection = connection; packetFilter = new AndFilter(new PacketExtensionFilter("offline", namespace), new PacketTypeFilter(Message.class)); } /** * 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 { final OfflineMessageRequest request = new OfflineMessageRequest(); request.setPurge(true); // Filter packets looking for an answer from the server. final PacketFilter responseFilter = new PacketIDFilter( request.getPacketID()); final 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. final 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 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<String> nodes) throws XMPPException { final OfflineMessageRequest request = new OfflineMessageRequest(); for (final String node : nodes) { final OfflineMessageRequest.Item item = new OfflineMessageRequest.Item( node); item.setAction("remove"); request.addItem(item); } // Filter packets looking for an answer from the server. final PacketFilter responseFilter = new PacketIDFilter( request.getPacketID()); final 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. final 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()); } } /** * Returns an iterator on <tt>OfflineMessageHeader</tt> 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 <tt>OfflineMessageHeader</tt> 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<OfflineMessageHeader> getHeaders() throws XMPPException { final List<OfflineMessageHeader> answer = new ArrayList<OfflineMessageHeader>(); final DiscoverItems items = ServiceDiscoveryManager.getInstanceFor( connection).discoverItems(null, namespace); for (final Iterator<Item> it = items.getItems(); it.hasNext();) { final DiscoverItems.Item item = it.next(); answer.add(new OfflineMessageHeader(item)); } return answer.iterator(); } /** * 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 { final DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor( connection).discoverInfo(null, namespace); final Form extendedInfo = Form.getFormFrom(info); if (extendedInfo != null) { final String value = extendedInfo.getField("number_of_messages") .getValues().next(); return Integer.parseInt(value); } return 0; } /** * Returns an Iterator with all the offline <tt>Messages</tt> 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 <tt>Messages</tt> 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<Message> getMessages() throws XMPPException { final List<Message> messages = new ArrayList<Message>(); final OfflineMessageRequest request = new OfflineMessageRequest(); request.setFetch(true); // Filter packets looking for an answer from the server. final PacketFilter responseFilter = new PacketIDFilter( request.getPacketID()); final PacketCollector response = connection .createPacketCollector(responseFilter); // Filter offline messages that were requested by this request final 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. final 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 the offline <tt>Messages</tt> 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 <tt>Messages</tt> 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<Message> getMessages(final List<String> nodes) throws XMPPException { final List<Message> messages = new ArrayList<Message>(); final OfflineMessageRequest request = new OfflineMessageRequest(); for (final String node : nodes) { final OfflineMessageRequest.Item item = new OfflineMessageRequest.Item( node); item.setAction("view"); request.addItem(item); } // Filter packets looking for an answer from the server. final PacketFilter responseFilter = new PacketIDFilter( request.getPacketID()); final PacketCollector response = connection .createPacketCollector(responseFilter); // Filter offline messages that were requested by this request final PacketFilter messageFilter = new AndFilter(packetFilter, new PacketFilter() { @Override public boolean accept(Packet packet) { final OfflineMessageInfo info = (OfflineMessageInfo) packet .getExtension("offline", namespace); return nodes.contains(info.getNode()); } }); final 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. final 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 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 { final DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor( connection).discoverInfo(null); return info.containsFeature(namespace); } }