001/**
002 *
003 * Copyright 2003-2007 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.jivesoftware.smackx.offline;
019
020import org.jivesoftware.smack.PacketCollector;
021import org.jivesoftware.smack.SmackException.NoResponseException;
022import org.jivesoftware.smack.SmackException.NotConnectedException;
023import org.jivesoftware.smack.XMPPConnection;
024import org.jivesoftware.smack.XMPPException.XMPPErrorException;
025import org.jivesoftware.smack.filter.AndFilter;
026import org.jivesoftware.smack.filter.PacketExtensionFilter;
027import org.jivesoftware.smack.filter.PacketFilter;
028import org.jivesoftware.smack.filter.PacketTypeFilter;
029import org.jivesoftware.smack.packet.Message;
030import org.jivesoftware.smack.packet.Packet;
031import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
032import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
033import org.jivesoftware.smackx.disco.packet.DiscoverItems;
034import org.jivesoftware.smackx.offline.packet.OfflineMessageInfo;
035import org.jivesoftware.smackx.offline.packet.OfflineMessageRequest;
036import org.jivesoftware.smackx.xdata.Form;
037
038import java.util.ArrayList;
039import java.util.List;
040
041/**
042 * The OfflineMessageManager helps manage offline messages even before the user has sent an
043 * available presence. When a user asks for his offline messages before sending an available
044 * presence then the server will not send a flood with all the offline messages when the user
045 * becomes online. The server will not send a flood with all the offline messages to the session
046 * that made the offline messages request or to any other session used by the user that becomes
047 * online.<p>
048 *
049 * Once the session that made the offline messages request has been closed and the user becomes
050 * offline in all the resources then the server will resume storing the messages offline and will
051 * send all the offline messages to the user when he becomes online. Therefore, the server will
052 * flood the user when he becomes online unless the user uses this class to manage his offline
053 * messages.
054 *
055 * @author Gaston Dombiak
056 */
057public class OfflineMessageManager {
058
059    private final static String namespace = "http://jabber.org/protocol/offline";
060
061    private XMPPConnection connection;
062
063    private PacketFilter packetFilter;
064
065    public OfflineMessageManager(XMPPConnection connection) {
066        this.connection = connection;
067        packetFilter =
068                new AndFilter(new PacketExtensionFilter("offline", namespace),
069                        new PacketTypeFilter(Message.class));
070    }
071
072    /**
073     * Returns true if the server supports Flexible Offline Message Retrieval. When the server
074     * supports Flexible Offline Message Retrieval it is possible to get the header of the offline
075     * messages, get specific messages, delete specific messages, etc.
076     *
077     * @return a boolean indicating if the server supports Flexible Offline Message Retrieval.
078     * @throws XMPPErrorException If the user is not allowed to make this request.
079     * @throws NoResponseException if there was no response from the server.
080     * @throws NotConnectedException 
081     */
082    public boolean supportsFlexibleRetrieval() throws NoResponseException, XMPPErrorException, NotConnectedException {
083        return ServiceDiscoveryManager.getInstanceFor(connection).supportsFeature(connection.getServiceName(), namespace);
084    }
085
086    /**
087     * Returns the number of offline messages for the user of the connection.
088     *
089     * @return the number of offline messages for the user of the connection.
090     * @throws XMPPErrorException If the user is not allowed to make this request or the server does
091     *                       not support offline message retrieval.
092     * @throws NoResponseException if there was no response from the server.
093     * @throws NotConnectedException 
094     */
095    public int getMessageCount() throws NoResponseException, XMPPErrorException, NotConnectedException {
096        DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null,
097                namespace);
098        Form extendedInfo = Form.getFormFrom(info);
099        if (extendedInfo != null) {
100            String value = extendedInfo.getField("number_of_messages").getValues().get(0);
101            return Integer.parseInt(value);
102        }
103        return 0;
104    }
105
106    /**
107     * Returns a List of <tt>OfflineMessageHeader</tt> that keep information about the
108     * offline message. The OfflineMessageHeader includes a stamp that could be used to retrieve
109     * the complete message or delete the specific message.
110     *
111     * @return a List of <tt>OfflineMessageHeader</tt> that keep information about the offline
112     *         message.
113     * @throws XMPPErrorException If the user is not allowed to make this request or the server does
114     *                       not support offline message retrieval.
115     * @throws NoResponseException if there was no response from the server.
116     * @throws NotConnectedException 
117     */
118    public List<OfflineMessageHeader> getHeaders() throws NoResponseException, XMPPErrorException, NotConnectedException {
119        List<OfflineMessageHeader> answer = new ArrayList<OfflineMessageHeader>();
120        DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection).discoverItems(
121                null, namespace);
122        for (DiscoverItems.Item item : items.getItems()) {
123            answer.add(new OfflineMessageHeader(item));
124        }
125        return answer;
126    }
127
128    /**
129     * Returns a List of the offline <tt>Messages</tt> whose stamp matches the specified
130     * request. The request will include the list of stamps that uniquely identifies
131     * the offline messages to retrieve. The returned offline messages will not be deleted
132     * from the server. Use {@link #deleteMessages(java.util.List)} to delete the messages.
133     *
134     * @param nodes the list of stamps that uniquely identifies offline message.
135     * @return a List with the offline <tt>Messages</tt> that were received as part of
136     *         this request.
137     * @throws XMPPErrorException If the user is not allowed to make this request or the server does
138     *                       not support offline message retrieval.
139     * @throws NoResponseException if there was no response from the server.
140     * @throws NotConnectedException 
141     */
142    public List<Message> getMessages(final List<String> nodes) throws NoResponseException, XMPPErrorException, NotConnectedException {
143        List<Message> messages = new ArrayList<Message>();
144        OfflineMessageRequest request = new OfflineMessageRequest();
145        for (String node : nodes) {
146            OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node);
147            item.setAction("view");
148            request.addItem(item);
149        }
150        // Filter offline messages that were requested by this request
151        PacketFilter messageFilter = new AndFilter(packetFilter, new PacketFilter() {
152            public boolean accept(Packet packet) {
153                OfflineMessageInfo info = (OfflineMessageInfo) packet.getExtension("offline",
154                        namespace);
155                return nodes.contains(info.getNode());
156            }
157        });
158        PacketCollector messageCollector = connection.createPacketCollector(messageFilter);
159        connection.createPacketCollectorAndSend(request).nextResultOrThrow();
160        // Collect the received offline messages
161        Message message = (Message) messageCollector.nextResult();
162        while (message != null) {
163            messages.add(message);
164            message = (Message) messageCollector.nextResult();
165        }
166        // Stop queuing offline messages
167        messageCollector.cancel();
168        return messages;
169    }
170
171    /**
172     * Returns an Iterator with all the offline <tt>Messages</tt> of the user. The returned offline
173     * messages will not be deleted from the server. Use {@link #deleteMessages(java.util.List)}
174     * to delete the messages.
175     *
176     * @return a List with all the offline <tt>Messages</tt> of the user.
177     * @throws XMPPErrorException If the user is not allowed to make this request or the server does
178     *                       not support offline message retrieval.
179     * @throws NoResponseException if there was no response from the server.
180     * @throws NotConnectedException 
181     */
182    public List<Message> getMessages() throws NoResponseException, XMPPErrorException, NotConnectedException {
183        List<Message> messages = new ArrayList<Message>();
184        OfflineMessageRequest request = new OfflineMessageRequest();
185        request.setFetch(true);
186        connection.createPacketCollectorAndSend(request).nextResultOrThrow();
187
188        PacketCollector messageCollector = connection.createPacketCollector(packetFilter);
189        // Collect the received offline messages
190        Message message = (Message) messageCollector.nextResult();
191        while (message != null) {
192            messages.add(message);
193            message = (Message) messageCollector.nextResult();
194        }
195        // Stop queuing offline messages
196        messageCollector.cancel();
197        return messages;
198    }
199
200    /**
201     * Deletes the specified list of offline messages. The request will include the list of
202     * stamps that uniquely identifies the offline messages to delete.
203     *
204     * @param nodes the list of stamps that uniquely identifies offline message.
205     * @throws XMPPErrorException If the user is not allowed to make this request or the server does
206     *                       not support offline message retrieval.
207     * @throws NoResponseException if there was no response from the server.
208     * @throws NotConnectedException 
209     */
210    public void deleteMessages(List<String> nodes) throws NoResponseException, XMPPErrorException, NotConnectedException {
211        OfflineMessageRequest request = new OfflineMessageRequest();
212        for (String node : nodes) {
213            OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node);
214            item.setAction("remove");
215            request.addItem(item);
216        }
217        connection.createPacketCollectorAndSend(request).nextResultOrThrow();
218    }
219
220    /**
221     * Deletes all offline messages of the user.
222     *
223     * @throws XMPPErrorException If the user is not allowed to make this request or the server does
224     *                       not support offline message retrieval.
225     * @throws NoResponseException if there was no response from the server.
226     * @throws NotConnectedException 
227     */
228    public void deleteMessages() throws NoResponseException, XMPPErrorException, NotConnectedException {
229        OfflineMessageRequest request = new OfflineMessageRequest();
230        request.setPurge(true);
231        connection.createPacketCollectorAndSend(request).nextResultOrThrow();
232    }
233}