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.StanzaExtensionFilter; 027import org.jivesoftware.smack.filter.StanzaFilter; 028import org.jivesoftware.smack.filter.StanzaTypeFilter; 029import org.jivesoftware.smack.packet.Message; 030import org.jivesoftware.smack.packet.Stanza; 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 final XMPPConnection connection; 062 063 private static final StanzaFilter PACKET_FILTER = new AndFilter(new StanzaExtensionFilter( 064 new OfflineMessageInfo()), StanzaTypeFilter.MESSAGE); 065 066 public OfflineMessageManager(XMPPConnection connection) { 067 this.connection = connection; 068 } 069 070 /** 071 * Returns true if the server supports Flexible Offline Message Retrieval. When the server 072 * supports Flexible Offline Message Retrieval it is possible to get the header of the offline 073 * messages, get specific messages, delete specific messages, etc. 074 * 075 * @return a boolean indicating if the server supports Flexible Offline Message Retrieval. 076 * @throws XMPPErrorException If the user is not allowed to make this request. 077 * @throws NoResponseException if there was no response from the server. 078 * @throws NotConnectedException 079 */ 080 public boolean supportsFlexibleRetrieval() throws NoResponseException, XMPPErrorException, NotConnectedException { 081 return ServiceDiscoveryManager.getInstanceFor(connection).serverSupportsFeature(namespace); 082 } 083 084 /** 085 * Returns the number of offline messages for the user of the connection. 086 * 087 * @return the number of offline messages for the user of the connection. 088 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 089 * not support offline message retrieval. 090 * @throws NoResponseException if there was no response from the server. 091 * @throws NotConnectedException 092 */ 093 public int getMessageCount() throws NoResponseException, XMPPErrorException, NotConnectedException { 094 DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null, 095 namespace); 096 Form extendedInfo = Form.getFormFrom(info); 097 if (extendedInfo != null) { 098 String value = extendedInfo.getField("number_of_messages").getValues().get(0); 099 return Integer.parseInt(value); 100 } 101 return 0; 102 } 103 104 /** 105 * Returns a List of <tt>OfflineMessageHeader</tt> that keep information about the 106 * offline message. The OfflineMessageHeader includes a stamp that could be used to retrieve 107 * the complete message or delete the specific message. 108 * 109 * @return a List of <tt>OfflineMessageHeader</tt> that keep information about the offline 110 * message. 111 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 112 * not support offline message retrieval. 113 * @throws NoResponseException if there was no response from the server. 114 * @throws NotConnectedException 115 */ 116 public List<OfflineMessageHeader> getHeaders() throws NoResponseException, XMPPErrorException, NotConnectedException { 117 List<OfflineMessageHeader> answer = new ArrayList<OfflineMessageHeader>(); 118 DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection).discoverItems( 119 null, namespace); 120 for (DiscoverItems.Item item : items.getItems()) { 121 answer.add(new OfflineMessageHeader(item)); 122 } 123 return answer; 124 } 125 126 /** 127 * Returns a List of the offline <tt>Messages</tt> whose stamp matches the specified 128 * request. The request will include the list of stamps that uniquely identifies 129 * the offline messages to retrieve. The returned offline messages will not be deleted 130 * from the server. Use {@link #deleteMessages(java.util.List)} to delete the messages. 131 * 132 * @param nodes the list of stamps that uniquely identifies offline message. 133 * @return a List with the offline <tt>Messages</tt> that were received as part of 134 * this request. 135 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 136 * not support offline message retrieval. 137 * @throws NoResponseException if there was no response from the server. 138 * @throws NotConnectedException 139 */ 140 public List<Message> getMessages(final List<String> nodes) throws NoResponseException, XMPPErrorException, NotConnectedException { 141 List<Message> messages = new ArrayList<Message>(); 142 OfflineMessageRequest request = new OfflineMessageRequest(); 143 for (String node : nodes) { 144 OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node); 145 item.setAction("view"); 146 request.addItem(item); 147 } 148 // Filter offline messages that were requested by this request 149 StanzaFilter messageFilter = new AndFilter(PACKET_FILTER, new StanzaFilter() { 150 public boolean accept(Stanza packet) { 151 OfflineMessageInfo info = (OfflineMessageInfo) packet.getExtension("offline", 152 namespace); 153 return nodes.contains(info.getNode()); 154 } 155 }); 156 PacketCollector messageCollector = connection.createPacketCollector(messageFilter); 157 try { 158 connection.createPacketCollectorAndSend(request).nextResultOrThrow(); 159 // Collect the received offline messages 160 Message message = messageCollector.nextResult(); 161 while (message != null) { 162 messages.add(message); 163 message = messageCollector.nextResult(); 164 } 165 } 166 finally { 167 // Stop queuing offline messages 168 messageCollector.cancel(); 169 } 170 return messages; 171 } 172 173 /** 174 * Returns a List of Messages with all the offline <tt>Messages</tt> of the user. The returned offline 175 * messages will not be deleted from the server. Use {@link #deleteMessages(java.util.List)} 176 * to delete the messages. 177 * 178 * @return a List with all the offline <tt>Messages</tt> of the user. 179 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 180 * not support offline message retrieval. 181 * @throws NoResponseException if there was no response from the server. 182 * @throws NotConnectedException 183 */ 184 public List<Message> getMessages() throws NoResponseException, XMPPErrorException, NotConnectedException { 185 OfflineMessageRequest request = new OfflineMessageRequest(); 186 request.setFetch(true); 187 188 PacketCollector resultCollector = connection.createPacketCollectorAndSend(request); 189 PacketCollector.Configuration messageCollectorConfiguration = PacketCollector.newConfiguration().setStanzaFilter(PACKET_FILTER).setCollectorToReset(resultCollector); 190 PacketCollector messageCollector = connection.createPacketCollector(messageCollectorConfiguration); 191 192 List<Message> messages = null; 193 try { 194 resultCollector.nextResultOrThrow(); 195 // Be extra safe, cancel the message collector right here so that it does not collector 196 // other messages that eventually match (although I've no idea how this could happen in 197 // case of XEP-13). 198 messageCollector.cancel(); 199 messages = new ArrayList<>(messageCollector.getCollectedCount()); 200 Message message; 201 while ((message = messageCollector.pollResult()) != null) { 202 messages.add(message); 203 } 204 } 205 finally { 206 // Ensure that the message collector is canceled even if nextResultOrThrow threw. It 207 // doesn't matter if we cancel the message collector twice 208 messageCollector.cancel(); 209 } 210 return messages; 211 } 212 213 /** 214 * Deletes the specified list of offline messages. The request will include the list of 215 * stamps that uniquely identifies the offline messages to delete. 216 * 217 * @param nodes the list of stamps that uniquely identifies offline message. 218 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 219 * not support offline message retrieval. 220 * @throws NoResponseException if there was no response from the server. 221 * @throws NotConnectedException 222 */ 223 public void deleteMessages(List<String> nodes) throws NoResponseException, XMPPErrorException, NotConnectedException { 224 OfflineMessageRequest request = new OfflineMessageRequest(); 225 for (String node : nodes) { 226 OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node); 227 item.setAction("remove"); 228 request.addItem(item); 229 } 230 connection.createPacketCollectorAndSend(request).nextResultOrThrow(); 231 } 232 233 /** 234 * Deletes all offline messages of the user. 235 * 236 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 237 * not support offline message retrieval. 238 * @throws NoResponseException if there was no response from the server. 239 * @throws NotConnectedException 240 */ 241 public void deleteMessages() throws NoResponseException, XMPPErrorException, NotConnectedException { 242 OfflineMessageRequest request = new OfflineMessageRequest(); 243 request.setPurge(true); 244 connection.createPacketCollectorAndSend(request).nextResultOrThrow(); 245 } 246}