001/** 002 * 003 * Copyright 2013-2014 Georg Lukas, 2015 Florian Schmaus 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 */ 017package org.jivesoftware.smackx.receipts; 018 019import java.util.Map; 020import java.util.Set; 021import java.util.WeakHashMap; 022import java.util.concurrent.CopyOnWriteArraySet; 023 024import org.jivesoftware.smack.SmackException; 025import org.jivesoftware.smack.SmackException.NotConnectedException; 026import org.jivesoftware.smack.XMPPConnection; 027import org.jivesoftware.smack.ConnectionCreationListener; 028import org.jivesoftware.smack.Manager; 029import org.jivesoftware.smack.StanzaListener; 030import org.jivesoftware.smack.XMPPConnectionRegistry; 031import org.jivesoftware.smack.XMPPException; 032import org.jivesoftware.smack.filter.AndFilter; 033import org.jivesoftware.smack.filter.MessageTypeFilter; 034import org.jivesoftware.smack.filter.StanzaFilter; 035import org.jivesoftware.smack.filter.StanzaExtensionFilter; 036import org.jivesoftware.smack.filter.StanzaTypeFilter; 037import org.jivesoftware.smack.packet.Message; 038import org.jivesoftware.smack.packet.Stanza; 039import org.jivesoftware.smack.roster.Roster; 040import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 041 042/** 043 * Manager for XEP-0184: Message Delivery Receipts. This class implements 044 * the manager for {@link DeliveryReceipt} support, enabling and disabling of 045 * automatic DeliveryReceipt transmission. 046 * 047 * <p> 048 * You can send delivery receipt requests and listen for incoming delivery receipts as shown in this example: 049 * </p> 050 * <pre> 051 * deliveryReceiptManager.addReceiptReceivedListener(new ReceiptReceivedListener() { 052 * void onReceiptReceived(String fromJid, String toJid, String receiptId, Stanza(/Packet) receipt) { 053 * // If the receiving entity does not support delivery receipts, 054 * // then the receipt received listener may not get invoked. 055 * } 056 * }); 057 * Message message = … 058 * DeliveryReceiptRequest.addTo(message); 059 * connection.sendStanza(message); 060 * </pre> 061 * 062 * DeliveryReceiptManager can be configured to automatically add delivery receipt requests to every 063 * message with {@link #autoAddDeliveryReceiptRequests()}. 064 * 065 * @author Georg Lukas 066 * @see <a href="http://xmpp.org/extensions/xep-0184.html">XEP-0184: Message Delivery Receipts</a> 067 */ 068public class DeliveryReceiptManager extends Manager { 069 070 private static final StanzaFilter MESSAGES_WITH_DEVLIERY_RECEIPT_REQUEST = new AndFilter(StanzaTypeFilter.MESSAGE, 071 new StanzaExtensionFilter(new DeliveryReceiptRequest())); 072 private static final StanzaFilter MESSAGES_WITH_DELIVERY_RECEIPT = new AndFilter(StanzaTypeFilter.MESSAGE, 073 new StanzaExtensionFilter(DeliveryReceipt.ELEMENT, DeliveryReceipt.NAMESPACE)); 074 075 private static Map<XMPPConnection, DeliveryReceiptManager> instances = new WeakHashMap<XMPPConnection, DeliveryReceiptManager>(); 076 077 static { 078 XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { 079 public void connectionCreated(XMPPConnection connection) { 080 getInstanceFor(connection); 081 } 082 }); 083 } 084 085 /** 086 * Specifies when incoming message delivery receipt requests should be automatically 087 * acknowledged with an receipt. 088 */ 089 public enum AutoReceiptMode { 090 091 /** 092 * Never send deliver receipts 093 */ 094 disabled, 095 096 /** 097 * Only send delivery receipts if the requester is subscribed to our presence. 098 */ 099 ifIsSubscribed, 100 101 /** 102 * Always send delivery receipts. <b>Warning:</b> this may causes presence leaks. See <a 103 * href="http://xmpp.org/extensions/xep-0184.html#security">XEP-0184: Message Delivery 104 * Receipts § 8. Security Considerations</a> 105 */ 106 always, 107 } 108 109 private static AutoReceiptMode defaultAutoReceiptMode = AutoReceiptMode.ifIsSubscribed; 110 111 /** 112 * Set the default automatic receipt mode for new connections. 113 * 114 * @param autoReceiptMode the default automatic receipt mode. 115 */ 116 public static void setDefaultAutoReceiptMode(AutoReceiptMode autoReceiptMode) { 117 defaultAutoReceiptMode = autoReceiptMode; 118 } 119 120 private AutoReceiptMode autoReceiptMode = defaultAutoReceiptMode; 121 122 private final Set<ReceiptReceivedListener> receiptReceivedListeners = new CopyOnWriteArraySet<ReceiptReceivedListener>(); 123 124 private DeliveryReceiptManager(XMPPConnection connection) { 125 super(connection); 126 ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection); 127 sdm.addFeature(DeliveryReceipt.NAMESPACE); 128 129 // Add the packet listener to handling incoming delivery receipts 130 connection.addAsyncStanzaListener(new StanzaListener() { 131 @Override 132 public void processPacket(Stanza packet) throws NotConnectedException { 133 DeliveryReceipt dr = DeliveryReceipt.from((Message) packet); 134 // notify listeners of incoming receipt 135 for (ReceiptReceivedListener l : receiptReceivedListeners) { 136 l.onReceiptReceived(packet.getFrom(), packet.getTo(), dr.getId(), packet); 137 } 138 } 139 }, MESSAGES_WITH_DELIVERY_RECEIPT); 140 141 // Add the packet listener to handle incoming delivery receipt requests 142 connection.addAsyncStanzaListener(new StanzaListener() { 143 @Override 144 public void processPacket(Stanza packet) throws NotConnectedException { 145 final String from = packet.getFrom(); 146 final XMPPConnection connection = connection(); 147 switch (autoReceiptMode) { 148 case disabled: 149 return; 150 case ifIsSubscribed: 151 if (!Roster.getInstanceFor(connection).isSubscribedToMyPresence(from)) { 152 return; 153 } 154 break; 155 case always: 156 break; 157 } 158 159 final Message messageWithReceiptRequest = (Message) packet; 160 Message ack = receiptMessageFor(messageWithReceiptRequest); 161 connection.sendStanza(ack); 162 } 163 }, MESSAGES_WITH_DEVLIERY_RECEIPT_REQUEST); 164 } 165 166 /** 167 * Obtain the DeliveryReceiptManager responsible for a connection. 168 * 169 * @param connection the connection object. 170 * 171 * @return the DeliveryReceiptManager instance for the given connection 172 */ 173 public static synchronized DeliveryReceiptManager getInstanceFor(XMPPConnection connection) { 174 DeliveryReceiptManager receiptManager = instances.get(connection); 175 176 if (receiptManager == null) { 177 receiptManager = new DeliveryReceiptManager(connection); 178 instances.put(connection, receiptManager); 179 } 180 181 return receiptManager; 182 } 183 184 /** 185 * Returns true if Delivery Receipts are supported by a given JID 186 * 187 * @param jid 188 * @return true if supported 189 * @throws SmackException if there was no response from the server. 190 * @throws XMPPException 191 */ 192 public boolean isSupported(String jid) throws SmackException, XMPPException { 193 return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(jid, 194 DeliveryReceipt.NAMESPACE); 195 } 196 197 /** 198 * Configure whether the {@link DeliveryReceiptManager} should automatically 199 * reply to incoming {@link DeliveryReceipt}s. 200 * 201 * @param autoReceiptMode the new auto receipt mode. 202 * @see AutoReceiptMode 203 */ 204 public void setAutoReceiptMode(AutoReceiptMode autoReceiptMode) { 205 this.autoReceiptMode = autoReceiptMode; 206 } 207 208 /** 209 * Get the currently active auto receipt mode. 210 * 211 * @return the currently active auto receipt mode. 212 */ 213 public AutoReceiptMode getAutoReceiptMode() { 214 return autoReceiptMode; 215 } 216 217 /** 218 * Get informed about incoming delivery receipts with a {@link ReceiptReceivedListener}. 219 * 220 * @param listener the listener to be informed about new receipts 221 */ 222 public void addReceiptReceivedListener(ReceiptReceivedListener listener) { 223 receiptReceivedListeners.add(listener); 224 } 225 226 /** 227 * Stop getting informed about incoming delivery receipts. 228 * 229 * @param listener the listener to be removed 230 */ 231 public void removeReceiptReceivedListener(ReceiptReceivedListener listener) { 232 receiptReceivedListeners.remove(listener); 233 } 234 235 private static final StanzaListener AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER = new StanzaListener() { 236 @Override 237 public void processPacket(Stanza packet) throws NotConnectedException { 238 Message message = (Message) packet; 239 DeliveryReceiptRequest.addTo(message); 240 } 241 }; 242 243 /** 244 * Enables automatic requests of delivery receipts for outgoing messages of type 'normal', 'chat' or 'headline. 245 * 246 * @since 4.1 247 * @see #dontAutoAddDeliveryReceiptRequests() 248 */ 249 public void autoAddDeliveryReceiptRequests() { 250 connection().addPacketSendingListener(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER, 251 MessageTypeFilter.NORMAL_OR_CHAT_OR_HEADLINE); 252 } 253 254 /** 255 * Disables automatically requests of delivery receipts for outgoing messages. 256 * 257 * @since 4.1 258 * @see #autoAddDeliveryReceiptRequests() 259 */ 260 public void dontAutoAddDeliveryReceiptRequests() { 261 connection().removePacketSendingListener(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER); 262 } 263 264 /** 265 * Test if a message requires a delivery receipt. 266 * 267 * @param message Stanza(/Packet) object to check for a DeliveryReceiptRequest 268 * 269 * @return true if a delivery receipt was requested 270 */ 271 public static boolean hasDeliveryReceiptRequest(Message message) { 272 return (DeliveryReceiptRequest.from(message) != null); 273 } 274 275 /** 276 * Add a delivery receipt request to an outgoing packet. 277 * 278 * Only message packets may contain receipt requests as of XEP-0184, 279 * therefore only allow Message as the parameter type. 280 * 281 * @param m Message object to add a request to 282 * @return the Message ID which will be used as receipt ID 283 * @deprecated use {@link DeliveryReceiptRequest#addTo(Message)} 284 */ 285 @Deprecated 286 public static String addDeliveryReceiptRequest(Message m) { 287 return DeliveryReceiptRequest.addTo(m); 288 } 289 290 /** 291 * Create and return a new message including a delivery receipt extension for the given message. 292 * 293 * @param messageWithReceiptRequest the given message with a receipt request extension. 294 * @return a new message with a receipt. 295 * @since 4.1 296 */ 297 public static Message receiptMessageFor(Message messageWithReceiptRequest) { 298 Message message = new Message(messageWithReceiptRequest.getFrom(), messageWithReceiptRequest.getType()); 299 message.addExtension(new DeliveryReceipt(messageWithReceiptRequest.getStanzaId())); 300 return message; 301 } 302}