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