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