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}