001/** 002 * 003 * Copyright 2013 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.Collections; 020import java.util.Map; 021import java.util.WeakHashMap; 022 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.PacketListener; 030import org.jivesoftware.smack.XMPPException; 031import org.jivesoftware.smack.XMPPException.XMPPErrorException; 032import org.jivesoftware.smack.filter.IQReplyFilter; 033import org.jivesoftware.smack.packet.IQ; 034import org.jivesoftware.smack.packet.Message; 035import org.jivesoftware.smack.packet.Packet; 036import org.jivesoftware.smackx.carbons.packet.CarbonExtension; 037import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 038 039/** 040 * Packet extension for XEP-0280: Message Carbons. This class implements 041 * the manager for registering {@link CarbonExtension} support, enabling and disabling 042 * message carbons. 043 * 044 * You should call enableCarbons() before sending your first undirected 045 * presence. 046 * 047 * @author Georg Lukas 048 */ 049public class CarbonManager extends Manager { 050 051 private static Map<XMPPConnection, CarbonManager> instances = 052 Collections.synchronizedMap(new WeakHashMap<XMPPConnection, CarbonManager>()); 053 054 static { 055 XMPPConnection.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 instances.put(connection, this); 069 } 070 071 /** 072 * Obtain the CarbonManager responsible for a connection. 073 * 074 * @param connection the connection object. 075 * 076 * @return a CarbonManager instance 077 */ 078 public static synchronized CarbonManager getInstanceFor(XMPPConnection connection) { 079 CarbonManager carbonManager = instances.get(connection); 080 081 if (carbonManager == null) { 082 carbonManager = new CarbonManager(connection); 083 } 084 085 return carbonManager; 086 } 087 088 private IQ carbonsEnabledIQ(final boolean new_state) { 089 IQ setIQ = new IQ() { 090 public String getChildElementXML() { 091 return "<" + (new_state? "enable" : "disable") + " xmlns='" + CarbonExtension.NAMESPACE + "'/>"; 092 } 093 }; 094 setIQ.setType(IQ.Type.SET); 095 return setIQ; 096 } 097 098 /** 099 * Returns true if XMPP Carbons are supported by the server. 100 * 101 * @return true if supported 102 * @throws SmackException if there was no response from the server. 103 * @throws XMPPException 104 */ 105 public boolean isSupportedByServer() throws XMPPException, SmackException { 106 return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature( 107 connection().getServiceName(), 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().addPacketListener(new PacketListener() { 123 public void processPacket(Packet packet) { 124 IQ result = (IQ)packet; 125 if (result.getType() == IQ.Type.RESULT) { 126 enabled_state = new_state; 127 } 128 connection().removePacketListener(this); 129 } 130 }, new IQReplyFilter(setIQ, connection())); 131 132 connection().sendPacket(setIQ); 133 } 134 135 /** 136 * Notify server to change the carbons state. This method blocks 137 * some time until the server replies to the IQ and returns true on 138 * success. 139 * 140 * You should first check for support using isSupportedByServer(). 141 * 142 * @param new_state whether carbons should be enabled or disabled 143 * @throws XMPPErrorException 144 * @throws NoResponseException 145 * @throws NotConnectedException 146 * 147 */ 148 public synchronized void setCarbonsEnabled(final boolean new_state) throws NoResponseException, 149 XMPPErrorException, NotConnectedException { 150 if (enabled_state == new_state) 151 return; 152 153 IQ setIQ = carbonsEnabledIQ(new_state); 154 155 connection().createPacketCollectorAndSend(setIQ).nextResultOrThrow(); 156 enabled_state = new_state; 157 } 158 159 /** 160 * Helper method to enable carbons. 161 * 162 * @throws XMPPException 163 * @throws SmackException if there was no response from the server. 164 */ 165 public void enableCarbons() throws XMPPException, SmackException { 166 setCarbonsEnabled(true); 167 } 168 169 /** 170 * Helper method to disable carbons. 171 * 172 * @throws XMPPException 173 * @throws SmackException if there was no response from the server. 174 */ 175 public void disableCarbons() throws XMPPException, SmackException { 176 setCarbonsEnabled(false); 177 } 178 179 /** 180 * Check if carbons are enabled on this connection. 181 */ 182 public boolean getCarbonsEnabled() { 183 return this.enabled_state; 184 } 185 186 /** 187 * Obtain a Carbon from a message, if available. 188 * 189 * @param msg Message object to check for carbons 190 * 191 * @return a Carbon if available, null otherwise. 192 */ 193 public static CarbonExtension getCarbon(Message msg) { 194 CarbonExtension cc = (CarbonExtension)msg.getExtension("received", CarbonExtension.NAMESPACE); 195 if (cc == null) 196 cc = (CarbonExtension)msg.getExtension("sent", CarbonExtension.NAMESPACE); 197 return cc; 198 } 199 200 /** 201 * Mark a message as "private", so it will not be carbon-copied. 202 * 203 * @param msg Message object to mark private 204 */ 205 public static void disableCarbons(Message msg) { 206 msg.addExtension(new CarbonExtension.Private()); 207 } 208}