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        try {
160            connection.createPacketCollectorAndSend(request).nextResultOrThrow();
161            // Collect the received offline messages
162            Message message = (Message) messageCollector.nextResult();
163            while (message != null) {
164                messages.add(message);
165                message = (Message) messageCollector.nextResult();
166            }
167        }
168        finally {
169            // Stop queuing offline messages
170            messageCollector.cancel();
171        }
172        return messages;
173    }
174
175    /**
176     * Returns a List of Messages with all the offline <tt>Messages</tt> of the user. The returned offline
177     * messages will not be deleted from the server. Use {@link #deleteMessages(java.util.List)}
178     * to delete the messages.
179     *
180     * @return a List with all the offline <tt>Messages</tt> of the user.
181     * @throws XMPPErrorException If the user is not allowed to make this request or the server does
182     *                       not support offline message retrieval.
183     * @throws NoResponseException if there was no response from the server.
184     * @throws NotConnectedException 
185     */
186    public List<Message> getMessages() throws NoResponseException, XMPPErrorException, NotConnectedException {
187        List<Message> messages = new ArrayList<Message>();
188        OfflineMessageRequest request = new OfflineMessageRequest();
189        request.setFetch(true);
190
191        PacketCollector messageCollector = connection.createPacketCollector(packetFilter);
192        try {
193            connection.createPacketCollectorAndSend(request).nextResultOrThrow();
194
195            // Collect the received offline messages
196            Message message = (Message) messageCollector.nextResult();
197            while (message != null) {
198                messages.add(message);
199                message = (Message) messageCollector.nextResult();
200            }
201        }
202        finally {
203            // Stop queuing offline messages
204            messageCollector.cancel();
205        }
206        return messages;
207    }
208
209    /**
210     * Deletes the specified list of offline messages. The request will include the list of
211     * stamps that uniquely identifies the offline messages to delete.
212     *
213     * @param nodes the list of stamps that uniquely identifies offline message.
214     * @throws XMPPErrorException If the user is not allowed to make this request or the server does
215     *                       not support offline message retrieval.
216     * @throws NoResponseException if there was no response from the server.
217     * @throws NotConnectedException 
218     */
219    public void deleteMessages(List<String> nodes) throws NoResponseException, XMPPErrorException, NotConnectedException {
220        OfflineMessageRequest request = new OfflineMessageRequest();
221        for (String node : nodes) {
222            OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node);
223            item.setAction("remove");
224            request.addItem(item);
225        }
226        connection.createPacketCollectorAndSend(request).nextResultOrThrow();
227    }
228
229    /**
230     * Deletes all offline messages of the user.
231     *
232     * @throws XMPPErrorException If the user is not allowed to make this request or the server does
233     *                       not support offline message retrieval.
234     * @throws NoResponseException if there was no response from the server.
235     * @throws NotConnectedException 
236     */
237    public void deleteMessages() throws NoResponseException, XMPPErrorException, NotConnectedException {
238        OfflineMessageRequest request = new OfflineMessageRequest();
239        request.setPurge(true);
240        connection.createPacketCollectorAndSend(request).nextResultOrThrow();
241    }
242}