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}