1/**
2 * $RCSfile$
3 * $Revision$
4 * $Date$
5 *
6 * Copyright 2003-2007 Jive Software.
7 *
8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21package org.jivesoftware.smackx;
22
23import org.jivesoftware.smack.PacketCollector;
24import org.jivesoftware.smack.SmackConfiguration;
25import org.jivesoftware.smack.Connection;
26import org.jivesoftware.smack.XMPPException;
27import org.jivesoftware.smack.filter.*;
28import org.jivesoftware.smack.packet.IQ;
29import org.jivesoftware.smack.packet.Message;
30import org.jivesoftware.smack.packet.Packet;
31import org.jivesoftware.smackx.packet.DiscoverInfo;
32import org.jivesoftware.smackx.packet.DiscoverItems;
33import org.jivesoftware.smackx.packet.OfflineMessageInfo;
34import org.jivesoftware.smackx.packet.OfflineMessageRequest;
35
36import java.util.ArrayList;
37import java.util.Iterator;
38import java.util.List;
39
40/**
41 * The OfflineMessageManager helps manage offline messages even before the user has sent an
42 * available presence. When a user asks for his offline messages before sending an available
43 * presence then the server will not send a flood with all the offline messages when the user
44 * becomes online. The server will not send a flood with all the offline messages to the session
45 * that made the offline messages request or to any other session used by the user that becomes
46 * online.<p>
47 *
48 * Once the session that made the offline messages request has been closed and the user becomes
49 * offline in all the resources then the server will resume storing the messages offline and will
50 * send all the offline messages to the user when he becomes online. Therefore, the server will
51 * flood the user when he becomes online unless the user uses this class to manage his offline
52 * messages.
53 *
54 * @author Gaston Dombiak
55 */
56public class OfflineMessageManager {
57
58    private final static String namespace = "http://jabber.org/protocol/offline";
59
60    private Connection connection;
61
62    private PacketFilter packetFilter;
63
64    public OfflineMessageManager(Connection connection) {
65        this.connection = connection;
66        packetFilter =
67                new AndFilter(new PacketExtensionFilter("offline", namespace),
68                        new PacketTypeFilter(Message.class));
69    }
70
71    /**
72     * Returns true if the server supports Flexible Offline Message Retrieval. When the server
73     * supports Flexible Offline Message Retrieval it is possible to get the header of the offline
74     * messages, get specific messages, delete specific messages, etc.
75     *
76     * @return a boolean indicating if the server supports Flexible Offline Message Retrieval.
77     * @throws XMPPException If the user is not allowed to make this request.
78     */
79    public boolean supportsFlexibleRetrieval() throws XMPPException {
80        DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(connection.getServiceName());
81        return info.containsFeature(namespace);
82    }
83
84    /**
85     * Returns the number of offline messages for the user of the connection.
86     *
87     * @return the number of offline messages for the user of the connection.
88     * @throws XMPPException If the user is not allowed to make this request or the server does
89     *                       not support offline message retrieval.
90     */
91    public int getMessageCount() throws XMPPException {
92        DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null,
93                namespace);
94        Form extendedInfo = Form.getFormFrom(info);
95        if (extendedInfo != null) {
96            String value = extendedInfo.getField("number_of_messages").getValues().next();
97            return Integer.parseInt(value);
98        }
99        return 0;
100    }
101
102    /**
103     * Returns an iterator on <tt>OfflineMessageHeader</tt> that keep information about the
104     * offline message. The OfflineMessageHeader includes a stamp that could be used to retrieve
105     * the complete message or delete the specific message.
106     *
107     * @return an iterator on <tt>OfflineMessageHeader</tt> that keep information about the offline
108     *         message.
109     * @throws XMPPException If the user is not allowed to make this request or the server does
110     *                       not support offline message retrieval.
111     */
112    public Iterator<OfflineMessageHeader> getHeaders() throws XMPPException {
113        List<OfflineMessageHeader> answer = new ArrayList<OfflineMessageHeader>();
114        DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection).discoverItems(
115                null, namespace);
116        for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext();) {
117            DiscoverItems.Item item = it.next();
118            answer.add(new OfflineMessageHeader(item));
119        }
120        return answer.iterator();
121    }
122
123    /**
124     * Returns an Iterator with the offline <tt>Messages</tt> whose stamp matches the specified
125     * request. The request will include the list of stamps that uniquely identifies
126     * the offline messages to retrieve. The returned offline messages will not be deleted
127     * from the server. Use {@link #deleteMessages(java.util.List)} to delete the messages.
128     *
129     * @param nodes the list of stamps that uniquely identifies offline message.
130     * @return an Iterator with the offline <tt>Messages</tt> that were received as part of
131     *         this request.
132     * @throws XMPPException If the user is not allowed to make this request or the server does
133     *                       not support offline message retrieval.
134     */
135    public Iterator<Message> getMessages(final List<String> nodes) throws XMPPException {
136        List<Message> messages = new ArrayList<Message>();
137        OfflineMessageRequest request = new OfflineMessageRequest();
138        for (String node : nodes) {
139            OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node);
140            item.setAction("view");
141            request.addItem(item);
142        }
143        // Filter packets looking for an answer from the server.
144        PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
145        PacketCollector response = connection.createPacketCollector(responseFilter);
146        // Filter offline messages that were requested by this request
147        PacketFilter messageFilter = new AndFilter(packetFilter, new PacketFilter() {
148            public boolean accept(Packet packet) {
149                OfflineMessageInfo info = (OfflineMessageInfo) packet.getExtension("offline",
150                        namespace);
151                return nodes.contains(info.getNode());
152            }
153        });
154        PacketCollector messageCollector = connection.createPacketCollector(messageFilter);
155        // Send the retrieval request to the server.
156        connection.sendPacket(request);
157        // Wait up to a certain number of seconds for a reply.
158        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
159        // Stop queuing results
160        response.cancel();
161
162        if (answer == null) {
163            throw new XMPPException("No response from server.");
164        } else if (answer.getError() != null) {
165            throw new XMPPException(answer.getError());
166        }
167
168        // Collect the received offline messages
169        Message message = (Message) messageCollector.nextResult(
170                SmackConfiguration.getPacketReplyTimeout());
171        while (message != null) {
172            messages.add(message);
173            message =
174                    (Message) messageCollector.nextResult(
175                            SmackConfiguration.getPacketReplyTimeout());
176        }
177        // Stop queuing offline messages
178        messageCollector.cancel();
179        return messages.iterator();
180    }
181
182    /**
183     * Returns an Iterator with all the offline <tt>Messages</tt> of the user. The returned offline
184     * messages will not be deleted from the server. Use {@link #deleteMessages(java.util.List)}
185     * to delete the messages.
186     *
187     * @return an Iterator with all the offline <tt>Messages</tt> of the user.
188     * @throws XMPPException If the user is not allowed to make this request or the server does
189     *                       not support offline message retrieval.
190     */
191    public Iterator<Message> getMessages() throws XMPPException {
192        List<Message> messages = new ArrayList<Message>();
193        OfflineMessageRequest request = new OfflineMessageRequest();
194        request.setFetch(true);
195        // Filter packets looking for an answer from the server.
196        PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
197        PacketCollector response = connection.createPacketCollector(responseFilter);
198        // Filter offline messages that were requested by this request
199        PacketCollector messageCollector = connection.createPacketCollector(packetFilter);
200        // Send the retrieval request to the server.
201        connection.sendPacket(request);
202        // Wait up to a certain number of seconds for a reply.
203        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
204        // Stop queuing results
205        response.cancel();
206
207        if (answer == null) {
208            throw new XMPPException("No response from server.");
209        } else if (answer.getError() != null) {
210            throw new XMPPException(answer.getError());
211        }
212
213        // Collect the received offline messages
214        Message message = (Message) messageCollector.nextResult(
215                SmackConfiguration.getPacketReplyTimeout());
216        while (message != null) {
217            messages.add(message);
218            message =
219                    (Message) messageCollector.nextResult(
220                            SmackConfiguration.getPacketReplyTimeout());
221        }
222        // Stop queuing offline messages
223        messageCollector.cancel();
224        return messages.iterator();
225    }
226
227    /**
228     * Deletes the specified list of offline messages. The request will include the list of
229     * stamps that uniquely identifies the offline messages to delete.
230     *
231     * @param nodes the list of stamps that uniquely identifies offline message.
232     * @throws XMPPException If the user is not allowed to make this request or the server does
233     *                       not support offline message retrieval.
234     */
235    public void deleteMessages(List<String> nodes) throws XMPPException {
236        OfflineMessageRequest request = new OfflineMessageRequest();
237        for (String node : nodes) {
238            OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node);
239            item.setAction("remove");
240            request.addItem(item);
241        }
242        // Filter packets looking for an answer from the server.
243        PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
244        PacketCollector response = connection.createPacketCollector(responseFilter);
245        // Send the deletion request to the server.
246        connection.sendPacket(request);
247        // Wait up to a certain number of seconds for a reply.
248        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
249        // Stop queuing results
250        response.cancel();
251
252        if (answer == null) {
253            throw new XMPPException("No response from server.");
254        } else if (answer.getError() != null) {
255            throw new XMPPException(answer.getError());
256        }
257    }
258
259    /**
260     * Deletes all offline messages of the user.
261     *
262     * @throws XMPPException If the user is not allowed to make this request or the server does
263     *                       not support offline message retrieval.
264     */
265    public void deleteMessages() throws XMPPException {
266        OfflineMessageRequest request = new OfflineMessageRequest();
267        request.setPurge(true);
268        // Filter packets looking for an answer from the server.
269        PacketFilter responseFilter = new PacketIDFilter(request.getPacketID());
270        PacketCollector response = connection.createPacketCollector(responseFilter);
271        // Send the deletion request to the server.
272        connection.sendPacket(request);
273        // Wait up to a certain number of seconds for a reply.
274        IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout());
275        // Stop queuing results
276        response.cancel();
277
278        if (answer == null) {
279            throw new XMPPException("No response from server.");
280        } else if (answer.getError() != null) {
281            throw new XMPPException(answer.getError());
282        }
283    }
284}
285