DeliveryReceiptManager.java

  1. /**
  2.  *
  3.  * Copyright 2013-2014 Georg Lukas, 2015 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.receipts;

  18. import java.util.Map;
  19. import java.util.Set;
  20. import java.util.WeakHashMap;
  21. import java.util.concurrent.CopyOnWriteArraySet;

  22. import org.jivesoftware.smack.SmackException;
  23. import org.jivesoftware.smack.SmackException.NotConnectedException;
  24. import org.jivesoftware.smack.XMPPConnection;
  25. import org.jivesoftware.smack.ConnectionCreationListener;
  26. import org.jivesoftware.smack.Manager;
  27. import org.jivesoftware.smack.StanzaListener;
  28. import org.jivesoftware.smack.XMPPConnectionRegistry;
  29. import org.jivesoftware.smack.XMPPException;
  30. import org.jivesoftware.smack.filter.AndFilter;
  31. import org.jivesoftware.smack.filter.MessageTypeFilter;
  32. import org.jivesoftware.smack.filter.StanzaFilter;
  33. import org.jivesoftware.smack.filter.StanzaExtensionFilter;
  34. import org.jivesoftware.smack.filter.StanzaTypeFilter;
  35. import org.jivesoftware.smack.packet.Message;
  36. import org.jivesoftware.smack.packet.Stanza;
  37. import org.jivesoftware.smack.roster.Roster;
  38. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  39. import org.jxmpp.jid.Jid;

  40. /**
  41.  * Manager for XEP-0184: Message Delivery Receipts. This class implements
  42.  * the manager for {@link DeliveryReceipt} support, enabling and disabling of
  43.  * automatic DeliveryReceipt transmission.
  44.  *
  45.  * <p>
  46.  * You can send delivery receipt requests and listen for incoming delivery receipts as shown in this example:
  47.  * </p>
  48.  * <pre>
  49.  * deliveryReceiptManager.addReceiptReceivedListener(new ReceiptReceivedListener() {
  50.  *   void onReceiptReceived(String fromJid, String toJid, String receiptId, Packet receipt) {
  51.  *     // If the receiving entity does not support delivery receipts,
  52.  *     // then the receipt received listener may not get invoked.
  53.  *   }
  54.  * });
  55.  * Message message = …
  56.  * DeliveryReceiptRequest.addTo(message);
  57.  * connection.sendStanza(message);
  58.  * </pre>
  59.  *
  60.  * DeliveryReceiptManager can be configured to automatically add delivery receipt requests to every
  61.  * message with {@link #autoAddDeliveryReceiptRequests()}.
  62.  *
  63.  * @author Georg Lukas
  64.  * @see <a href="http://xmpp.org/extensions/xep-0184.html">XEP-0184: Message Delivery Receipts</a>
  65.  */
  66. public class DeliveryReceiptManager extends Manager {

  67.     private static final StanzaFilter MESSAGES_WITH_DEVLIERY_RECEIPT_REQUEST = new AndFilter(StanzaTypeFilter.MESSAGE,
  68.                     new StanzaExtensionFilter(new DeliveryReceiptRequest()));
  69.     private static final StanzaFilter MESSAGES_WITH_DELIVERY_RECEIPT = new AndFilter(StanzaTypeFilter.MESSAGE,
  70.                     new StanzaExtensionFilter(DeliveryReceipt.ELEMENT, DeliveryReceipt.NAMESPACE));

  71.     private static Map<XMPPConnection, DeliveryReceiptManager> instances = new WeakHashMap<XMPPConnection, DeliveryReceiptManager>();

  72.     static {
  73.         XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
  74.             public void connectionCreated(XMPPConnection connection) {
  75.                 getInstanceFor(connection);
  76.             }
  77.         });
  78.     }

  79.     /**
  80.      * Specifies when incoming message delivery receipt requests should be automatically
  81.      * acknowledged with an receipt.
  82.      */
  83.     public enum AutoReceiptMode {

  84.         /**
  85.          * Never send deliver receipts
  86.          */
  87.         disabled,

  88.         /**
  89.          * Only send delivery receipts if the requester is subscribed to our presence.
  90.          */
  91.         ifIsSubscribed,

  92.         /**
  93.          * Always send delivery receipts. <b>Warning:</b> this may causes presence leaks. See <a
  94.          * href="http://xmpp.org/extensions/xep-0184.html#security">XEP-0184: Message Delivery
  95.          * Receipts § 8. Security Considerations</a>
  96.          */
  97.         always,
  98.     }

  99.     private static AutoReceiptMode defaultAutoReceiptMode = AutoReceiptMode.ifIsSubscribed;

  100.     /**
  101.      * Set the default automatic receipt mode for new connections.
  102.      *
  103.      * @param autoReceiptMode the default automatic receipt mode.
  104.      */
  105.     public static void setDefaultAutoReceiptMode(AutoReceiptMode autoReceiptMode) {
  106.         defaultAutoReceiptMode = autoReceiptMode;
  107.     }

  108.     private AutoReceiptMode autoReceiptMode = defaultAutoReceiptMode;

  109.     private final Set<ReceiptReceivedListener> receiptReceivedListeners = new CopyOnWriteArraySet<ReceiptReceivedListener>();

  110.     private DeliveryReceiptManager(XMPPConnection connection) {
  111.         super(connection);
  112.         ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection);
  113.         sdm.addFeature(DeliveryReceipt.NAMESPACE);

  114.         // Add the packet listener to handling incoming delivery receipts
  115.         connection.addAsyncStanzaListener(new StanzaListener() {
  116.             @Override
  117.             public void processPacket(Stanza packet) throws NotConnectedException {
  118.                 DeliveryReceipt dr = DeliveryReceipt.from((Message) packet);
  119.                 // notify listeners of incoming receipt
  120.                 for (ReceiptReceivedListener l : receiptReceivedListeners) {
  121.                     l.onReceiptReceived(packet.getFrom(), packet.getTo(), dr.getId(), packet);
  122.                 }
  123.             }
  124.         }, MESSAGES_WITH_DELIVERY_RECEIPT);

  125.         // Add the packet listener to handle incoming delivery receipt requests
  126.         connection.addAsyncStanzaListener(new StanzaListener() {
  127.             @Override
  128.             public void processPacket(Stanza packet) throws NotConnectedException, InterruptedException {
  129.                 final Jid from = packet.getFrom();
  130.                 final XMPPConnection connection = connection();
  131.                 switch (autoReceiptMode) {
  132.                 case disabled:
  133.                     return;
  134.                 case ifIsSubscribed:
  135.                     if (!Roster.getInstanceFor(connection).isSubscribedToMyPresence(from)) {
  136.                         return;
  137.                     }
  138.                     break;
  139.                 case always:
  140.                     break;
  141.                 }

  142.                 final Message messageWithReceiptRequest = (Message) packet;
  143.                 Message ack = receiptMessageFor(messageWithReceiptRequest);
  144.                 connection.sendStanza(ack);
  145.             }
  146.         }, MESSAGES_WITH_DEVLIERY_RECEIPT_REQUEST);
  147.     }

  148.     /**
  149.      * Obtain the DeliveryReceiptManager responsible for a connection.
  150.      *
  151.      * @param connection the connection object.
  152.      *
  153.      * @return the DeliveryReceiptManager instance for the given connection
  154.      */
  155.      public static synchronized DeliveryReceiptManager getInstanceFor(XMPPConnection connection) {
  156.         DeliveryReceiptManager receiptManager = instances.get(connection);

  157.         if (receiptManager == null) {
  158.             receiptManager = new DeliveryReceiptManager(connection);
  159.             instances.put(connection, receiptManager);
  160.         }

  161.         return receiptManager;
  162.     }

  163.     /**
  164.      * Returns true if Delivery Receipts are supported by a given JID
  165.      *
  166.      * @param jid
  167.      * @return true if supported
  168.      * @throws SmackException if there was no response from the server.
  169.      * @throws XMPPException
  170.      * @throws InterruptedException
  171.      */
  172.     public boolean isSupported(Jid jid) throws SmackException, XMPPException, InterruptedException {
  173.         return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(jid,
  174.                         DeliveryReceipt.NAMESPACE);
  175.     }

  176.     /**
  177.      * Configure whether the {@link DeliveryReceiptManager} should automatically
  178.      * reply to incoming {@link DeliveryReceipt}s.
  179.      *
  180.      * @param autoReceiptMode the new auto receipt mode.
  181.      * @see AutoReceiptMode
  182.      */
  183.     public void setAutoReceiptMode(AutoReceiptMode autoReceiptMode) {
  184.         this.autoReceiptMode = autoReceiptMode;
  185.     }

  186.     /**
  187.      * Get the currently active auto receipt mode.
  188.      *
  189.      * @return the currently active auto receipt mode.
  190.      */
  191.     public AutoReceiptMode getAutoReceiptMode() {
  192.         return autoReceiptMode;
  193.     }

  194.     /**
  195.      * Get informed about incoming delivery receipts with a {@link ReceiptReceivedListener}.
  196.      *
  197.      * @param listener the listener to be informed about new receipts
  198.      */
  199.     public void addReceiptReceivedListener(ReceiptReceivedListener listener) {
  200.         receiptReceivedListeners.add(listener);
  201.     }

  202.     /**
  203.      * Stop getting informed about incoming delivery receipts.
  204.      *
  205.      * @param listener the listener to be removed
  206.      */
  207.     public void removeReceiptReceivedListener(ReceiptReceivedListener listener) {
  208.         receiptReceivedListeners.remove(listener);
  209.     }

  210.     private static final StanzaListener AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER = new StanzaListener() {
  211.         @Override
  212.         public void processPacket(Stanza packet) throws NotConnectedException {
  213.             Message message = (Message) packet;
  214.             DeliveryReceiptRequest.addTo(message);
  215.         }
  216.     };

  217.     /**
  218.      * Enables automatic requests of delivery receipts for outgoing messages of type 'normal', 'chat' or 'headline.
  219.      *
  220.      * @since 4.1
  221.      * @see #dontAutoAddDeliveryReceiptRequests()
  222.      */
  223.     public void autoAddDeliveryReceiptRequests() {
  224.         connection().addPacketSendingListener(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER,
  225.                         MessageTypeFilter.NORMAL_OR_CHAT_OR_HEADLINE);
  226.     }

  227.     /**
  228.      * Disables automatically requests of delivery receipts for outgoing messages.
  229.      *
  230.      * @since 4.1
  231.      * @see #autoAddDeliveryReceiptRequests()
  232.      */
  233.     public void dontAutoAddDeliveryReceiptRequests() {
  234.         connection().removePacketSendingListener(AUTO_ADD_DELIVERY_RECEIPT_REQUESTS_LISTENER);
  235.     }

  236.     /**
  237.      * Test if a message requires a delivery receipt.
  238.      *
  239.      * @param message Packet object to check for a DeliveryReceiptRequest
  240.      *
  241.      * @return true if a delivery receipt was requested
  242.      */
  243.     public static boolean hasDeliveryReceiptRequest(Message message) {
  244.         return (DeliveryReceiptRequest.from(message) != null);
  245.     }

  246.     /**
  247.      * Add a delivery receipt request to an outgoing packet.
  248.      *
  249.      * Only message packets may contain receipt requests as of XEP-0184,
  250.      * therefore only allow Message as the parameter type.
  251.      *
  252.      * @param m Message object to add a request to
  253.      * @return the Message ID which will be used as receipt ID
  254.      * @deprecated use {@link DeliveryReceiptRequest#addTo(Message)}
  255.      */
  256.     @Deprecated
  257.     public static String addDeliveryReceiptRequest(Message m) {
  258.         return DeliveryReceiptRequest.addTo(m);
  259.     }

  260.     /**
  261.      * Create and return a new message including a delivery receipt extension for the given message.
  262.      *
  263.      * @param messageWithReceiptRequest the given message with a receipt request extension.
  264.      * @return a new message with a receipt.
  265.      * @since 4.1
  266.      */
  267.     public static Message receiptMessageFor(Message messageWithReceiptRequest) {
  268.         Message message = new Message(messageWithReceiptRequest.getFrom(), messageWithReceiptRequest.getType());
  269.         message.addExtension(new DeliveryReceipt(messageWithReceiptRequest.getStanzaId()));
  270.         return message;
  271.     }
  272. }