001/** 002 * 003 * Copyright 2016-2017 Fernando Ramirez, 2016-2020 Florian Schmaus 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.blocking; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.WeakHashMap; 025import java.util.concurrent.CopyOnWriteArraySet; 026 027import org.jivesoftware.smack.ConnectionCreationListener; 028import org.jivesoftware.smack.ConnectionListener; 029import org.jivesoftware.smack.Manager; 030import org.jivesoftware.smack.SmackException.NoResponseException; 031import org.jivesoftware.smack.SmackException.NotConnectedException; 032import org.jivesoftware.smack.XMPPConnection; 033import org.jivesoftware.smack.XMPPConnectionRegistry; 034import org.jivesoftware.smack.XMPPException.XMPPErrorException; 035import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler; 036import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode; 037import org.jivesoftware.smack.packet.IQ; 038import org.jivesoftware.smack.packet.IQ.Type; 039 040import org.jivesoftware.smackx.blocking.element.BlockContactsIQ; 041import org.jivesoftware.smackx.blocking.element.BlockListIQ; 042import org.jivesoftware.smackx.blocking.element.UnblockContactsIQ; 043import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 044 045import org.jxmpp.jid.Jid; 046 047/** 048 * Blocking command manager class. 049 * 050 * @author Fernando Ramirez 051 * @author Florian Schmaus 052 * @see <a href="http://xmpp.org/extensions/xep-0191.html">XEP-0191: Blocking 053 * Command</a> 054 */ 055public final class BlockingCommandManager extends Manager { 056 057 public static final String NAMESPACE = "urn:xmpp:blocking"; 058 059 private volatile List<Jid> blockListCached; 060 061 static { 062 XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { 063 @Override 064 public void connectionCreated(XMPPConnection connection) { 065 getInstanceFor(connection); 066 } 067 }); 068 } 069 070 private static final Map<XMPPConnection, BlockingCommandManager> INSTANCES = new WeakHashMap<>(); 071 072 /** 073 * Get the singleton instance of BlockingCommandManager. 074 * 075 * @param connection TODO javadoc me please 076 * @return the instance of BlockingCommandManager 077 */ 078 public static synchronized BlockingCommandManager getInstanceFor(XMPPConnection connection) { 079 BlockingCommandManager blockingCommandManager = INSTANCES.get(connection); 080 081 if (blockingCommandManager == null) { 082 blockingCommandManager = new BlockingCommandManager(connection); 083 INSTANCES.put(connection, blockingCommandManager); 084 } 085 086 return blockingCommandManager; 087 } 088 089 private final Set<AllJidsUnblockedListener> allJidsUnblockedListeners = new CopyOnWriteArraySet<>(); 090 091 private final Set<JidsBlockedListener> jidsBlockedListeners = new CopyOnWriteArraySet<>(); 092 093 private final Set<JidsUnblockedListener> jidsUnblockedListeners = new CopyOnWriteArraySet<>(); 094 095 private BlockingCommandManager(XMPPConnection connection) { 096 super(connection); 097 098 // block IQ handler 099 connection.registerIQRequestHandler( 100 new AbstractIqRequestHandler(BlockContactsIQ.ELEMENT, BlockContactsIQ.NAMESPACE, Type.set, Mode.sync) { 101 @Override 102 public IQ handleIQRequest(IQ iqRequest) { 103 BlockContactsIQ blockContactIQ = (BlockContactsIQ) iqRequest; 104 105 if (blockListCached == null) { 106 blockListCached = new ArrayList<>(); 107 } 108 109 List<Jid> blockedJids = blockContactIQ.getJids(); 110 blockListCached.addAll(blockedJids); 111 112 for (JidsBlockedListener listener : jidsBlockedListeners) { 113 listener.onJidsBlocked(blockedJids); 114 } 115 116 return IQ.createResultIQ(blockContactIQ); 117 } 118 }); 119 120 // unblock IQ handler 121 connection.registerIQRequestHandler(new AbstractIqRequestHandler(UnblockContactsIQ.ELEMENT, 122 UnblockContactsIQ.NAMESPACE, Type.set, Mode.sync) { 123 @Override 124 public IQ handleIQRequest(IQ iqRequest) { 125 UnblockContactsIQ unblockContactIQ = (UnblockContactsIQ) iqRequest; 126 127 if (blockListCached == null) { 128 blockListCached = new ArrayList<>(); 129 } 130 131 List<Jid> unblockedJids = unblockContactIQ.getJids(); 132 if (unblockedJids == null) { // remove all 133 blockListCached.clear(); 134 for (AllJidsUnblockedListener listener : allJidsUnblockedListeners) { 135 listener.onAllJidsUnblocked(); 136 } 137 } else { // remove only some 138 blockListCached.removeAll(unblockedJids); 139 for (JidsUnblockedListener listener : jidsUnblockedListeners) { 140 listener.onJidsUnblocked(unblockedJids); 141 } 142 } 143 144 return IQ.createResultIQ(unblockContactIQ); 145 } 146 }); 147 148 connection.addConnectionListener(new ConnectionListener() { 149 @Override 150 public void authenticated(XMPPConnection connection, boolean resumed) { 151 // No need to reset the cache if the connection got resumed. 152 if (resumed) { 153 return; 154 } 155 blockListCached = null; 156 } 157 }); 158 } 159 160 /** 161 * Returns true if Blocking Command is supported by the server. 162 * 163 * @return true if Blocking Command is supported by the server. 164 * @throws NoResponseException if there was no response from the remote entity. 165 * @throws XMPPErrorException if there was an XMPP error returned. 166 * @throws NotConnectedException if the XMPP connection is not connected. 167 * @throws InterruptedException if the calling thread was interrupted. 168 */ 169 public boolean isSupportedByServer() 170 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 171 return ServiceDiscoveryManager.getInstanceFor(connection()).serverSupportsFeature(NAMESPACE); 172 } 173 174 /** 175 * Returns the block list. 176 * 177 * @return the blocking list 178 * @throws NoResponseException if there was no response from the remote entity. 179 * @throws XMPPErrorException if there was an XMPP error returned. 180 * @throws NotConnectedException if the XMPP connection is not connected. 181 * @throws InterruptedException if the calling thread was interrupted. 182 */ 183 public List<Jid> getBlockList() 184 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 185 186 if (blockListCached == null) { 187 BlockListIQ blockListIQ = new BlockListIQ(); 188 BlockListIQ blockListIQResult = connection().createStanzaCollectorAndSend(blockListIQ).nextResultOrThrow(); 189 blockListCached = blockListIQResult.getBlockedJidsCopy(); 190 } 191 192 return Collections.unmodifiableList(blockListCached); 193 } 194 195 /** 196 * Block contacts. 197 * 198 * @param jids TODO javadoc me please 199 * @throws NoResponseException if there was no response from the remote entity. 200 * @throws XMPPErrorException if there was an XMPP error returned. 201 * @throws NotConnectedException if the XMPP connection is not connected. 202 * @throws InterruptedException if the calling thread was interrupted. 203 */ 204 public void blockContacts(List<Jid> jids) 205 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 206 BlockContactsIQ blockContactIQ = new BlockContactsIQ(jids); 207 connection().createStanzaCollectorAndSend(blockContactIQ).nextResultOrThrow(); 208 } 209 210 /** 211 * Unblock contacts. 212 * 213 * @param jids TODO javadoc me please 214 * @throws NoResponseException if there was no response from the remote entity. 215 * @throws XMPPErrorException if there was an XMPP error returned. 216 * @throws NotConnectedException if the XMPP connection is not connected. 217 * @throws InterruptedException if the calling thread was interrupted. 218 */ 219 public void unblockContacts(List<Jid> jids) 220 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 221 UnblockContactsIQ unblockContactIQ = new UnblockContactsIQ(jids); 222 connection().createStanzaCollectorAndSend(unblockContactIQ).nextResultOrThrow(); 223 } 224 225 /** 226 * Unblock all. 227 * 228 * @throws NoResponseException if there was no response from the remote entity. 229 * @throws XMPPErrorException if there was an XMPP error returned. 230 * @throws NotConnectedException if the XMPP connection is not connected. 231 * @throws InterruptedException if the calling thread was interrupted. 232 */ 233 public void unblockAll() 234 throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { 235 UnblockContactsIQ unblockContactIQ = new UnblockContactsIQ(); 236 connection().createStanzaCollectorAndSend(unblockContactIQ).nextResultOrThrow(); 237 } 238 239 public void addJidsBlockedListener(JidsBlockedListener jidsBlockedListener) { 240 jidsBlockedListeners.add(jidsBlockedListener); 241 } 242 243 public void removeJidsBlockedListener(JidsBlockedListener jidsBlockedListener) { 244 jidsBlockedListeners.remove(jidsBlockedListener); 245 } 246 247 public void addJidsUnblockedListener(JidsUnblockedListener jidsUnblockedListener) { 248 jidsUnblockedListeners.add(jidsUnblockedListener); 249 } 250 251 public void removeJidsUnblockedListener(JidsUnblockedListener jidsUnblockedListener) { 252 jidsUnblockedListeners.remove(jidsUnblockedListener); 253 } 254 255 public void addAllJidsUnblockedListener(AllJidsUnblockedListener allJidsUnblockedListener) { 256 allJidsUnblockedListeners.add(allJidsUnblockedListener); 257 } 258 259 public void removeAllJidsUnblockedListener(AllJidsUnblockedListener allJidsUnblockedListener) { 260 allJidsUnblockedListeners.remove(allJidsUnblockedListener); 261 } 262}