1 /** 2 * Copyright 2013 Georg Lukas 3 * 4 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.jivesoftware.smackx.carbons; 18 19 import java.util.Collections; 20 import java.util.Map; 21 import java.util.WeakHashMap; 22 23 import org.jivesoftware.smack.Connection; 24 import org.jivesoftware.smack.ConnectionCreationListener; 25 import org.jivesoftware.smack.PacketCollector; 26 import org.jivesoftware.smack.PacketListener; 27 import org.jivesoftware.smack.SmackConfiguration; 28 import org.jivesoftware.smack.XMPPException; 29 import org.jivesoftware.smack.filter.PacketIDFilter; 30 import org.jivesoftware.smack.packet.IQ; 31 import org.jivesoftware.smack.packet.Message; 32 import org.jivesoftware.smack.packet.Packet; 33 import org.jivesoftware.smackx.ServiceDiscoveryManager; 34 import org.jivesoftware.smackx.packet.DiscoverInfo; 35 36 /** 37 * Packet extension for XEP-0280: Message Carbons. This class implements 38 * the manager for registering {@link Carbon} support, enabling and disabling 39 * message carbons. 40 * 41 * You should call enableCarbons() before sending your first undirected 42 * presence. 43 * 44 * @author Georg Lukas 45 */ 46 public class CarbonManager { 47 48 private static Map<Connection, CarbonManager> instances = 49 Collections.synchronizedMap(new WeakHashMap<Connection, CarbonManager>()); 50 51 static { 52 Connection.addConnectionCreationListener(new ConnectionCreationListener() { 53 public void connectionCreated(Connection connection) { 54 new CarbonManager(connection); 55 } 56 }); 57 } 58 59 private Connection connection; 60 private volatile boolean enabled_state = false; 61 62 private CarbonManager(Connection connection) { 63 ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection); 64 sdm.addFeature(Carbon.NAMESPACE); 65 this.connection = connection; 66 instances.put(connection, this); 67 } 68 69 /** 70 * Obtain the CarbonManager responsible for a connection. 71 * 72 * @param connection the connection object. 73 * 74 * @return a CarbonManager instance 75 */ 76 public static CarbonManager getInstanceFor(Connection connection) { 77 CarbonManager carbonManager = instances.get(connection); 78 79 if (carbonManager == null) { 80 carbonManager = new CarbonManager(connection); 81 } 82 83 return carbonManager; 84 } 85 86 private IQ carbonsEnabledIQ(final boolean new_state) { 87 IQ setIQ = new IQ() { 88 public String getChildElementXML() { 89 return "<" + (new_state? "enable" : "disable") + " xmlns='" + Carbon.NAMESPACE + "'/>"; 90 } 91 }; 92 setIQ.setType(IQ.Type.SET); 93 return setIQ; 94 } 95 96 /** 97 * Returns true if XMPP Carbons are supported by the server. 98 * 99 * @return true if supported 100 */ 101 public boolean isSupportedByServer() { 102 try { 103 DiscoverInfo result = ServiceDiscoveryManager 104 .getInstanceFor(connection).discoverInfo(connection.getServiceName()); 105 return result.containsFeature(Carbon.NAMESPACE); 106 } 107 catch (XMPPException e) { 108 return false; 109 } 110 } 111 112 /** 113 * Notify server to change the carbons state. This method returns 114 * immediately and changes the variable when the reply arrives. 115 * 116 * You should first check for support using isSupportedByServer(). 117 * 118 * @param new_state whether carbons should be enabled or disabled 119 */ 120 public void sendCarbonsEnabled(final boolean new_state) { 121 IQ setIQ = carbonsEnabledIQ(new_state); 122 123 connection.addPacketListener(new PacketListener() { 124 public void processPacket(Packet packet) { 125 IQ result = (IQ)packet; 126 if (result.getType() == IQ.Type.RESULT) { 127 enabled_state = new_state; 128 } 129 connection.removePacketListener(this); 130 } 131 }, new PacketIDFilter(setIQ.getPacketID())); 132 133 connection.sendPacket(setIQ); 134 } 135 136 /** 137 * Notify server to change the carbons state. This method blocks 138 * some time until the server replies to the IQ and returns true on 139 * success. 140 * 141 * You should first check for support using isSupportedByServer(). 142 * 143 * @param new_state whether carbons should be enabled or disabled 144 * 145 * @return true if the operation was successful 146 */ 147 public boolean setCarbonsEnabled(final boolean new_state) { 148 if (enabled_state == new_state) 149 return true; 150 151 IQ setIQ = carbonsEnabledIQ(new_state); 152 153 PacketCollector collector = 154 connection.createPacketCollector(new PacketIDFilter(setIQ.getPacketID())); 155 connection.sendPacket(setIQ); 156 IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); 157 collector.cancel(); 158 159 if (result != null && result.getType() == IQ.Type.RESULT) { 160 enabled_state = new_state; 161 return true; 162 } 163 return false; 164 } 165 166 /** 167 * Helper method to enable carbons. 168 * 169 * @return true if the operation was successful 170 */ 171 public boolean enableCarbons() { 172 return setCarbonsEnabled(true); 173 } 174 175 /** 176 * Helper method to disable carbons. 177 * 178 * @return true if the operation was successful 179 */ 180 public boolean disableCarbons() { 181 return setCarbonsEnabled(false); 182 } 183 184 /** 185 * Check if carbons are enabled on this connection. 186 */ 187 public boolean getCarbonsEnabled() { 188 return this.enabled_state; 189 } 190 191 /** 192 * Obtain a Carbon from a message, if available. 193 * 194 * @param msg Message object to check for carbons 195 * 196 * @return a Carbon if available, null otherwise. 197 */ 198 public static Carbon getCarbon(Message msg) { 199 Carbon cc = (Carbon)msg.getExtension("received", Carbon.NAMESPACE); 200 if (cc == null) 201 cc = (Carbon)msg.getExtension("sent", Carbon.NAMESPACE); 202 return cc; 203 } 204 205 /** 206 * Mark a message as "private", so it will not be carbon-copied. 207 * 208 * @param msg Message object to mark private 209 */ 210 public static void disableCarbons(Message msg) { 211 msg.addExtension(new Carbon.Private()); 212 } 213 } 214