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