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