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