001/**
002 *
003 * Copyright 2013-2014 Georg Lukas
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.Collections;
020import java.util.HashSet;
021import java.util.Map;
022import java.util.Set;
023import java.util.WeakHashMap;
024
025import org.jivesoftware.smack.SmackException;
026import org.jivesoftware.smack.SmackException.NotConnectedException;
027import org.jivesoftware.smack.XMPPConnection;
028import org.jivesoftware.smack.ConnectionCreationListener;
029import org.jivesoftware.smack.Manager;
030import org.jivesoftware.smack.PacketListener;
031import org.jivesoftware.smack.XMPPException;
032import org.jivesoftware.smack.filter.PacketExtensionFilter;
033import org.jivesoftware.smack.packet.Message;
034import org.jivesoftware.smack.packet.Packet;
035import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
036
037/**
038 * Manager for XEP-0184: Message Delivery Receipts. This class implements
039 * the manager for {@link DeliveryReceipt} support, enabling and disabling of
040 * automatic DeliveryReceipt transmission.
041 *
042 * @author Georg Lukas
043 */
044public class DeliveryReceiptManager extends Manager implements PacketListener {
045
046    private static Map<XMPPConnection, DeliveryReceiptManager> instances = new WeakHashMap<XMPPConnection, DeliveryReceiptManager>();
047
048    static {
049        XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() {
050            public void connectionCreated(XMPPConnection connection) {
051                getInstanceFor(connection);
052            }
053        });
054    }
055
056    private boolean auto_receipts_enabled = false;
057    private Set<ReceiptReceivedListener> receiptReceivedListeners = Collections
058            .synchronizedSet(new HashSet<ReceiptReceivedListener>());
059
060    private DeliveryReceiptManager(XMPPConnection connection) {
061        super(connection);
062        ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection);
063        sdm.addFeature(DeliveryReceipt.NAMESPACE);
064
065        // register listener for delivery receipts and requests
066        connection.addPacketListener(this, new PacketExtensionFilter(DeliveryReceipt.NAMESPACE));
067    }
068
069    /**
070     * Obtain the DeliveryReceiptManager responsible for a connection.
071     *
072     * @param connection the connection object.
073     *
074     * @return the DeliveryReceiptManager instance for the given connection
075     */
076     public static synchronized DeliveryReceiptManager getInstanceFor(XMPPConnection connection) {
077        DeliveryReceiptManager receiptManager = instances.get(connection);
078
079        if (receiptManager == null) {
080            receiptManager = new DeliveryReceiptManager(connection);
081            instances.put(connection, receiptManager);
082        }
083
084        return receiptManager;
085    }
086
087    /**
088     * Returns true if Delivery Receipts are supported by a given JID
089     * 
090     * @param jid
091     * @return true if supported
092     * @throws SmackException if there was no response from the server.
093     * @throws XMPPException 
094     */
095    public boolean isSupported(String jid) throws SmackException, XMPPException {
096        return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(jid,
097                        DeliveryReceipt.NAMESPACE);
098    }
099
100    // handle incoming receipts and receipt requests
101    @Override
102    public void processPacket(Packet packet) throws NotConnectedException {
103        DeliveryReceipt dr = DeliveryReceipt.getFrom(packet);
104        if (dr != null) {
105            // notify listeners of incoming receipt
106            for (ReceiptReceivedListener l : receiptReceivedListeners) {
107                l.onReceiptReceived(packet.getFrom(), packet.getTo(), dr.getId());
108            }
109        }
110
111        // if enabled, automatically send a receipt
112        if (auto_receipts_enabled) {
113            DeliveryReceiptRequest drr = DeliveryReceiptRequest.getFrom(packet);
114            if (drr != null) {
115                XMPPConnection connection = connection();
116                Message ack = new Message(packet.getFrom(), Message.Type.normal);
117                ack.addExtension(new DeliveryReceipt(packet.getPacketID()));
118                connection.sendPacket(ack);
119            }
120        }
121    }
122
123    /**
124     * Configure whether the {@link DeliveryReceiptManager} should automatically
125     * reply to incoming {@link DeliveryReceipt}s. By default, this feature is off.
126     *
127     * @param new_state whether automatic transmission of
128     *                  DeliveryReceipts should be enabled or disabled
129     */
130    public void setAutoReceiptsEnabled(boolean new_state) {
131        auto_receipts_enabled = new_state;
132    }
133
134    /**
135     * Helper method to enable automatic DeliveryReceipt transmission.
136     */
137    public void enableAutoReceipts() {
138        setAutoReceiptsEnabled(true);
139    }
140
141    /**
142     * Helper method to disable automatic DeliveryReceipt transmission.
143     */
144    public void disableAutoReceipts() {
145        setAutoReceiptsEnabled(false);
146    }
147
148    /**
149     * Check if AutoReceipts are enabled on this connection.
150     */
151    public boolean getAutoReceiptsEnabled() {
152        return this.auto_receipts_enabled;
153    }
154
155    /**
156     * Get informed about incoming delivery receipts with a {@link ReceiptReceivedListener}.
157     * 
158     * @param listener the listener to be informed about new receipts
159     */
160    public void addReceiptReceivedListener(ReceiptReceivedListener listener) {
161        receiptReceivedListeners.add(listener);
162    }
163
164    /**
165     * Stop getting informed about incoming delivery receipts.
166     * 
167     * @param listener the listener to be removed
168     */
169    public void removeReceiptReceivedListener(ReceiptReceivedListener listener) {
170        receiptReceivedListeners.remove(listener);
171    }
172
173    /**
174     * Test if a packet requires a delivery receipt.
175     *
176     * @param p Packet object to check for a DeliveryReceiptRequest
177     *
178     * @return true if a delivery receipt was requested
179     */
180    public static boolean hasDeliveryReceiptRequest(Packet p) {
181        return (DeliveryReceiptRequest.getFrom(p) != null);
182    }
183
184    /**
185     * Add a delivery receipt request to an outgoing packet.
186     *
187     * Only message packets may contain receipt requests as of XEP-0184,
188     * therefore only allow Message as the parameter type.
189     *
190     * @param m Message object to add a request to
191     * @return the Message ID which will be used as receipt ID
192     */
193    public static String addDeliveryReceiptRequest(Message m) {
194        m.addExtension(new DeliveryReceiptRequest());
195        return m.getPacketID();
196    }
197}