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