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 java.util.ArrayList; 021import java.util.List; 022 023import org.jivesoftware.smack.SmackException.NoResponseException; 024import org.jivesoftware.smack.SmackException.NotConnectedException; 025import org.jivesoftware.smack.StanzaCollector; 026import org.jivesoftware.smack.XMPPConnection; 027import org.jivesoftware.smack.XMPPException.XMPPErrorException; 028import org.jivesoftware.smack.filter.AndFilter; 029import org.jivesoftware.smack.filter.StanzaExtensionFilter; 030import org.jivesoftware.smack.filter.StanzaFilter; 031import org.jivesoftware.smack.filter.StanzaTypeFilter; 032import org.jivesoftware.smack.packet.IQ; 033import org.jivesoftware.smack.packet.Message; 034import org.jivesoftware.smack.packet.Stanza; 035 036import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 037import org.jivesoftware.smackx.disco.packet.DiscoverInfo; 038import org.jivesoftware.smackx.disco.packet.DiscoverItems; 039import org.jivesoftware.smackx.offline.packet.OfflineMessageInfo; 040import org.jivesoftware.smackx.offline.packet.OfflineMessageRequest; 041import org.jivesoftware.smackx.xdata.Form; 042 043/** 044 * The OfflineMessageManager helps manage offline messages even before the user has sent an 045 * available presence. When a user asks for his offline messages before sending an available 046 * presence then the server will not send a flood with all the offline messages when the user 047 * becomes online. The server will not send a flood with all the offline messages to the session 048 * that made the offline messages request or to any other session used by the user that becomes 049 * online.<p> 050 * 051 * Once the session that made the offline messages request has been closed and the user becomes 052 * offline in all the resources then the server will resume storing the messages offline and will 053 * send all the offline messages to the user when he becomes online. Therefore, the server will 054 * flood the user when he becomes online unless the user uses this class to manage his offline 055 * messages. 056 * 057 * @author Gaston Dombiak 058 */ 059public class OfflineMessageManager { 060 061 private final static String namespace = "http://jabber.org/protocol/offline"; 062 063 private final XMPPConnection connection; 064 065 private static final StanzaFilter PACKET_FILTER = new AndFilter(new StanzaExtensionFilter( 066 new OfflineMessageInfo()), StanzaTypeFilter.MESSAGE); 067 068 public OfflineMessageManager(XMPPConnection connection) { 069 this.connection = connection; 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 * @throws InterruptedException 082 */ 083 public boolean supportsFlexibleRetrieval() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 084 return ServiceDiscoveryManager.getInstanceFor(connection).serverSupportsFeature(namespace); 085 } 086 087 /** 088 * Returns the number of offline messages for the user of the connection. 089 * 090 * @return the number of offline messages for the user of the connection. 091 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 092 * not support offline message retrieval. 093 * @throws NoResponseException if there was no response from the server. 094 * @throws NotConnectedException 095 * @throws InterruptedException 096 */ 097 public int getMessageCount() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 098 DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null, 099 namespace); 100 Form extendedInfo = Form.getFormFrom(info); 101 if (extendedInfo != null) { 102 String value = extendedInfo.getField("number_of_messages").getValues().get(0); 103 return Integer.parseInt(value); 104 } 105 return 0; 106 } 107 108 /** 109 * Returns a List of <tt>OfflineMessageHeader</tt> that keep information about the 110 * offline message. The OfflineMessageHeader includes a stamp that could be used to retrieve 111 * the complete message or delete the specific message. 112 * 113 * @return a List of <tt>OfflineMessageHeader</tt> that keep information about the offline 114 * message. 115 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 116 * not support offline message retrieval. 117 * @throws NoResponseException if there was no response from the server. 118 * @throws NotConnectedException 119 * @throws InterruptedException 120 */ 121 public List<OfflineMessageHeader> getHeaders() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 122 List<OfflineMessageHeader> answer = new ArrayList<OfflineMessageHeader>(); 123 DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection).discoverItems( 124 null, namespace); 125 for (DiscoverItems.Item item : items.getItems()) { 126 answer.add(new OfflineMessageHeader(item)); 127 } 128 return answer; 129 } 130 131 /** 132 * Returns a List of the offline <tt>Messages</tt> whose stamp matches the specified 133 * request. The request will include the list of stamps that uniquely identifies 134 * the offline messages to retrieve. The returned offline messages will not be deleted 135 * from the server. Use {@link #deleteMessages(java.util.List)} to delete the messages. 136 * 137 * @param nodes the list of stamps that uniquely identifies offline message. 138 * @return a List with the offline <tt>Messages</tt> that were received as part of 139 * this request. 140 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 141 * not support offline message retrieval. 142 * @throws NoResponseException if there was no response from the server. 143 * @throws NotConnectedException 144 * @throws InterruptedException 145 */ 146 public List<Message> getMessages(final List<String> nodes) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 147 List<Message> messages = new ArrayList<Message>(); 148 OfflineMessageRequest request = new OfflineMessageRequest(); 149 for (String node : nodes) { 150 OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node); 151 item.setAction("view"); 152 request.addItem(item); 153 } 154 // Filter offline messages that were requested by this request 155 StanzaFilter messageFilter = new AndFilter(PACKET_FILTER, new StanzaFilter() { 156 @Override 157 public boolean accept(Stanza packet) { 158 OfflineMessageInfo info = (OfflineMessageInfo) packet.getExtension("offline", 159 namespace); 160 return nodes.contains(info.getNode()); 161 } 162 }); 163 int pendingNodes = nodes.size(); 164 StanzaCollector messageCollector = connection.createStanzaCollector(messageFilter); 165 try { 166 connection.createStanzaCollectorAndSend(request).nextResultOrThrow(); 167 // Collect the received offline messages 168 Message message = messageCollector.nextResult(); 169 while (message != null && pendingNodes > 0) { 170 pendingNodes--; 171 messages.add(message); 172 message = messageCollector.nextResult(); 173 } 174 } 175 finally { 176 // Stop queuing offline messages 177 messageCollector.cancel(); 178 } 179 return messages; 180 } 181 182 /** 183 * Returns a List of Messages 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 a List with all the offline <tt>Messages</tt> of the user. 188 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 189 * not support offline message retrieval. 190 * @throws NoResponseException if there was no response from the server. 191 * @throws NotConnectedException 192 * @throws InterruptedException 193 */ 194 public List<Message> getMessages() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 195 OfflineMessageRequest request = new OfflineMessageRequest(); 196 request.setFetch(true); 197 198 StanzaCollector resultCollector = connection.createStanzaCollectorAndSend(request); 199 StanzaCollector.Configuration messageCollectorConfiguration = StanzaCollector.newConfiguration().setStanzaFilter(PACKET_FILTER).setCollectorToReset(resultCollector); 200 StanzaCollector messageCollector = connection.createStanzaCollector(messageCollectorConfiguration); 201 202 List<Message> messages = null; 203 try { 204 resultCollector.nextResultOrThrow(); 205 // Be extra safe, cancel the message collector right here so that it does not collector 206 // other messages that eventually match (although I've no idea how this could happen in 207 // case of XEP-13). 208 messageCollector.cancel(); 209 messages = new ArrayList<>(messageCollector.getCollectedCount()); 210 Message message; 211 while ((message = messageCollector.pollResult()) != null) { 212 messages.add(message); 213 } 214 } 215 finally { 216 // Ensure that the message collector is canceled even if nextResultOrThrow threw. It 217 // doesn't matter if we cancel the message collector twice 218 messageCollector.cancel(); 219 } 220 return messages; 221 } 222 223 /** 224 * Deletes the specified list of offline messages. The request will include the list of 225 * stamps that uniquely identifies the offline messages to delete. 226 * 227 * @param nodes the list of stamps that uniquely identifies offline message. 228 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 229 * not support offline message retrieval. 230 * @throws NoResponseException if there was no response from the server. 231 * @throws NotConnectedException 232 * @throws InterruptedException 233 */ 234 public void deleteMessages(List<String> nodes) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 235 OfflineMessageRequest request = new OfflineMessageRequest(); 236 request.setType(IQ.Type.set); 237 for (String node : nodes) { 238 OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node); 239 item.setAction("remove"); 240 request.addItem(item); 241 } 242 connection.createStanzaCollectorAndSend(request).nextResultOrThrow(); 243 } 244 245 /** 246 * Deletes all offline messages of the user. 247 * 248 * @throws XMPPErrorException If the user is not allowed to make this request or the server does 249 * not support offline message retrieval. 250 * @throws NoResponseException if there was no response from the server. 251 * @throws NotConnectedException 252 * @throws InterruptedException 253 */ 254 public void deleteMessages() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 255 OfflineMessageRequest request = new OfflineMessageRequest(); 256 request.setType(IQ.Type.set); 257 request.setPurge(true); 258 connection.createStanzaCollectorAndSend(request).nextResultOrThrow(); 259 } 260}