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}