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.carbons;
018
019import java.util.Map;
020import java.util.WeakHashMap;
021
022import org.jivesoftware.smack.AbstractConnectionListener;
023import org.jivesoftware.smack.SmackException;
024import org.jivesoftware.smack.SmackException.NoResponseException;
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.XMPPException.XMPPErrorException;
033import org.jivesoftware.smack.packet.IQ;
034import org.jivesoftware.smack.packet.Message;
035import org.jivesoftware.smack.packet.Stanza;
036import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
037import org.jivesoftware.smackx.carbons.packet.Carbon;
038import org.jivesoftware.smackx.carbons.packet.CarbonExtension.Private;
039import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
040
041/**
042 * Stanza(/Packet) extension for XEP-0280: Message Carbons. This class implements
043 * the manager for registering {@link CarbonExtension} support, enabling and disabling
044 * message carbons.
045 *
046 * You should call enableCarbons() before sending your first undirected
047 * presence.
048 *
049 * @author Georg Lukas
050 */
051public class CarbonManager extends Manager {
052
053    private static Map<XMPPConnection, CarbonManager> INSTANCES = new WeakHashMap<XMPPConnection, CarbonManager>();
054
055    static {
056        XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
057            public void connectionCreated(XMPPConnection connection) {
058                getInstanceFor(connection);
059            }
060        });
061    }
062    
063    private volatile boolean enabled_state = false;
064
065    private CarbonManager(XMPPConnection connection) {
066        super(connection);
067        ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection);
068        sdm.addFeature(CarbonExtension.NAMESPACE);
069        connection.addConnectionListener(new AbstractConnectionListener() {
070            @Override
071            public void connectionClosed() {
072                // Reset the state if the connection was cleanly closed. Note that this is not strictly necessary,
073                // because we also reset in authenticated() if the stream got not resumed, but for maximum correctness,
074                // also reset here.
075                enabled_state = false;
076            }
077            @Override
078            public void authenticated(XMPPConnection connection, boolean resumed) {
079                if (!resumed) {
080                    // Non-resumed XMPP sessions always start with disabled carbons
081                    enabled_state = false;
082                }
083            }
084        });
085    }
086
087    /**
088     * Obtain the CarbonManager responsible for a connection.
089     *
090     * @param connection the connection object.
091     *
092     * @return a CarbonManager instance
093     */
094    public static synchronized CarbonManager getInstanceFor(XMPPConnection connection) {
095        CarbonManager carbonManager = INSTANCES.get(connection);
096
097        if (carbonManager == null) {
098            carbonManager = new CarbonManager(connection);
099            INSTANCES.put(connection, carbonManager);
100        }
101
102        return carbonManager;
103    }
104
105    private static IQ carbonsEnabledIQ(final boolean new_state) {
106        IQ request;
107        if (new_state) {
108            request = new Carbon.Enable();
109        } else {
110            request = new Carbon.Disable();
111        }
112        return request;
113    }
114
115    /**
116     * Returns true if XMPP Carbons are supported by the server.
117     * 
118     * @return true if supported
119     * @throws NotConnectedException 
120     * @throws XMPPErrorException 
121     * @throws NoResponseException 
122     */
123    public boolean isSupportedByServer() throws NoResponseException, XMPPErrorException, NotConnectedException {
124        return ServiceDiscoveryManager.getInstanceFor(connection()).serverSupportsFeature(CarbonExtension.NAMESPACE);
125    }
126
127    /**
128     * Notify server to change the carbons state. This method returns
129     * immediately and changes the variable when the reply arrives.
130     *
131     * You should first check for support using isSupportedByServer().
132     *
133     * @param new_state whether carbons should be enabled or disabled
134     * @throws NotConnectedException 
135     */
136    public void sendCarbonsEnabled(final boolean new_state) throws NotConnectedException {
137        IQ setIQ = carbonsEnabledIQ(new_state);
138
139        connection().sendIqWithResponseCallback(setIQ, new StanzaListener() {
140            public void processPacket(Stanza packet) {
141                enabled_state = new_state;
142            }
143        });
144    }
145
146    /**
147     * Notify server to change the carbons state. This method blocks
148     * some time until the server replies to the IQ and returns true on
149     * success.
150     *
151     * You should first check for support using isSupportedByServer().
152     *
153     * @param new_state whether carbons should be enabled or disabled
154     * @throws XMPPErrorException 
155     * @throws NoResponseException 
156     * @throws NotConnectedException 
157     *
158     */
159    public synchronized void setCarbonsEnabled(final boolean new_state) throws NoResponseException,
160                    XMPPErrorException, NotConnectedException {
161        if (enabled_state == new_state)
162            return;
163
164        IQ setIQ = carbonsEnabledIQ(new_state);
165
166        connection().createPacketCollectorAndSend(setIQ).nextResultOrThrow();
167        enabled_state = new_state;
168    }
169
170    /**
171     * Helper method to enable carbons.
172     *
173     * @throws XMPPException 
174     * @throws SmackException if there was no response from the server.
175     */
176    public void enableCarbons() throws XMPPException, SmackException {
177        setCarbonsEnabled(true);
178    }
179
180    /**
181     * Helper method to disable carbons.
182     *
183     * @throws XMPPException 
184     * @throws SmackException if there was no response from the server.
185     */
186    public void disableCarbons() throws XMPPException, SmackException {
187        setCarbonsEnabled(false);
188    }
189
190    /**
191     * Check if carbons are enabled on this connection.
192     */
193    public boolean getCarbonsEnabled() {
194        return this.enabled_state;
195    }
196
197    /**
198     * Mark a message as "private", so it will not be carbon-copied.
199     *
200     * @param msg Message object to mark private
201     * @deprecated use {@link Private#addTo(Message)}
202     */
203    @Deprecated
204    public static void disableCarbons(Message msg) {
205        msg.addExtension(Private.INSTANCE);
206    }
207}