001/**
002 *
003 * Copyright 2006-2007 Jive Software.
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.privacy;
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;
025
026import org.jivesoftware.smack.SmackException.NoResponseException;
027import org.jivesoftware.smack.SmackException.NotConnectedException;
028import org.jivesoftware.smack.XMPPConnection;
029import org.jivesoftware.smack.ConnectionCreationListener;
030import org.jivesoftware.smack.Manager;
031import org.jivesoftware.smack.PacketListener;
032import org.jivesoftware.smack.XMPPException.XMPPErrorException;
033import org.jivesoftware.smack.filter.AndFilter;
034import org.jivesoftware.smack.filter.IQTypeFilter;
035import org.jivesoftware.smack.filter.PacketExtensionFilter;
036import org.jivesoftware.smack.filter.PacketFilter;
037import org.jivesoftware.smack.packet.IQ;
038import org.jivesoftware.smack.packet.Packet;
039import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
040import org.jivesoftware.smackx.privacy.packet.Privacy;
041import org.jivesoftware.smackx.privacy.packet.PrivacyItem;
042
043/**
044 * A PrivacyListManager is used by XMPP clients to block or allow communications from other
045 * users. Use the manager to: <ul>
046 *      <li>Retrieve privacy lists.
047 *      <li>Add, remove, and edit privacy lists.
048 *      <li>Set, change, or decline active lists.
049 *      <li>Set, change, or decline the default list (i.e., the list that is active by default).
050 * </ul>
051 * Privacy Items can handle different kind of permission communications based on JID, group, 
052 * subscription type or globally (@see PrivacyItem).
053 * 
054 * @author Francisco Vives
055 * @see <a href="http://xmpp.org/extensions/xep-0016.html">XEP-16: Privacy Lists</a>
056 */
057public class PrivacyListManager extends Manager {
058    public static final String NAMESPACE = "jabber:iq:privacy";
059
060    private static final PacketFilter PACKET_FILTER = new AndFilter(new IQTypeFilter(IQ.Type.SET),
061                    new PacketExtensionFilter("query", "jabber:iq:privacy"));
062
063    // Keep the list of instances of this class.
064    private static final Map<XMPPConnection, PrivacyListManager> instances = Collections
065            .synchronizedMap(new WeakHashMap<XMPPConnection, PrivacyListManager>());
066
067        private final List<PrivacyListListener> listeners = new ArrayList<PrivacyListListener>();
068
069    static {
070        // Create a new PrivacyListManager on every established connection.
071        XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() {
072            public void connectionCreated(XMPPConnection connection) {
073                getInstanceFor(connection);
074            }
075        });
076    }
077
078    /**
079     * Creates a new privacy manager to maintain the communication privacy. Note: no
080     * information is sent to or received from the server until you attempt to 
081     * get or set the privacy communication.<p>
082     *
083     * @param connection the XMPP connection.
084     */
085        private PrivacyListManager(final XMPPConnection connection) {
086        super(connection);
087        // Register the new instance and associate it with the connection 
088        instances.put(connection, this);
089
090        connection.addPacketListener(new PacketListener() {
091            @Override
092            public void processPacket(Packet packet) throws NotConnectedException {
093                Privacy privacy = (Privacy) packet;
094
095                // Notifies the event to the listeners.
096                synchronized (listeners) {
097                    for (PrivacyListListener listener : listeners) {
098                        // Notifies the created or updated privacy lists
099                        for (Map.Entry<String,List<PrivacyItem>> entry : privacy.getItemLists().entrySet()) {
100                            String listName = entry.getKey();
101                            List<PrivacyItem> items = entry.getValue();
102                            if (items.isEmpty()) {
103                                listener.updatedPrivacyList(listName);
104                            } else {
105                                listener.setPrivacyList(listName, items);
106                            }
107                        }
108                    }
109                }
110
111                // Send a result package acknowledging the reception of a privacy package.
112                IQ iq = IQ.createResultIQ(privacy);
113                connection.sendPacket(iq);
114            }
115        }, PACKET_FILTER);
116    }
117
118        /** Answer the connection userJID that owns the privacy.
119         * @return the userJID that owns the privacy
120         */
121        private String getUser() {
122                return connection().getUser();
123        }
124
125    /**
126     * Returns the PrivacyListManager instance associated with a given XMPPConnection.
127     * 
128     * @param connection the connection used to look for the proper PrivacyListManager.
129     * @return the PrivacyListManager associated with a given XMPPConnection.
130     */
131    public static synchronized PrivacyListManager getInstanceFor(XMPPConnection connection) {
132        PrivacyListManager plm = instances.get(connection);
133        if (plm == null) plm = new PrivacyListManager(connection);
134        return plm;
135    }
136
137        /**
138         * Send the {@link Privacy} packet to the server in order to know some privacy content and then 
139         * waits for the answer.
140         * 
141         * @param requestPrivacy is the {@link Privacy} packet configured properly whose XML
142     *      will be sent to the server.
143         * @return a new {@link Privacy} with the data received from the server.
144         * @throws XMPPErrorException 
145         * @throws NoResponseException 
146         * @throws NotConnectedException 
147         */ 
148        private Privacy getRequest(Privacy requestPrivacy) throws NoResponseException, XMPPErrorException, NotConnectedException  {
149                // The request is a get iq type
150                requestPrivacy.setType(Privacy.Type.GET);
151                requestPrivacy.setFrom(this.getUser());
152
153        Privacy privacyAnswer = (Privacy) connection().createPacketCollectorAndSend(requestPrivacy).nextResultOrThrow();
154        return privacyAnswer;
155        }
156
157    /**
158     * Send the {@link Privacy} packet to the server in order to modify the server privacy and waits
159     * for the answer.
160     * 
161     * @param requestPrivacy is the {@link Privacy} packet configured properly whose xml will be
162     *        sent to the server.
163     * @return a new {@link Privacy} with the data received from the server.
164     * @throws XMPPErrorException 
165     * @throws NoResponseException 
166     * @throws NotConnectedException 
167     */
168    private Packet setRequest(Privacy requestPrivacy) throws NoResponseException, XMPPErrorException, NotConnectedException  {
169        // The request is a get iq type
170        requestPrivacy.setType(Privacy.Type.SET);
171        requestPrivacy.setFrom(this.getUser());
172
173        return connection().createPacketCollectorAndSend(requestPrivacy).nextResultOrThrow();
174    }
175
176        /**
177         * Answer a privacy containing the list structure without {@link PrivacyItem}.
178         * 
179         * @return a Privacy with the list names.
180         * @throws XMPPErrorException 
181         * @throws NoResponseException 
182         * @throws NotConnectedException 
183         */ 
184        private Privacy getPrivacyWithListNames() throws NoResponseException, XMPPErrorException, NotConnectedException {
185                // The request of the list is an empty privacy message
186                Privacy request = new Privacy();
187                
188                // Send the package to the server and get the answer
189                return getRequest(request);
190        }
191
192    /**
193     * Answer the active privacy list.
194     * 
195     * @return the privacy list of the active list.
196     * @throws XMPPErrorException 
197     * @throws NoResponseException 
198     * @throws NotConnectedException 
199     */ 
200    public PrivacyList getActiveList() throws NoResponseException, XMPPErrorException, NotConnectedException  {
201        Privacy privacyAnswer = this.getPrivacyWithListNames();
202        String listName = privacyAnswer.getActiveName();
203        boolean isDefaultAndActive = privacyAnswer.getActiveName() != null
204                && privacyAnswer.getDefaultName() != null
205                && privacyAnswer.getActiveName().equals(
206                privacyAnswer.getDefaultName());
207        return new PrivacyList(true, isDefaultAndActive, listName, getPrivacyListItems(listName));
208    }
209
210    /**
211     * Answer the default privacy list.
212     * 
213     * @return the privacy list of the default list.
214     * @throws XMPPErrorException 
215     * @throws NoResponseException 
216     * @throws NotConnectedException 
217     */ 
218    public PrivacyList getDefaultList() throws NoResponseException, XMPPErrorException, NotConnectedException {
219        Privacy privacyAnswer = this.getPrivacyWithListNames();
220        String listName = privacyAnswer.getDefaultName();
221        boolean isDefaultAndActive = privacyAnswer.getActiveName() != null
222                && privacyAnswer.getDefaultName() != null
223                && privacyAnswer.getActiveName().equals(
224                privacyAnswer.getDefaultName());
225        return new PrivacyList(isDefaultAndActive, true, listName, getPrivacyListItems(listName));
226    }
227
228    /**
229     * Answer the privacy list items under listName with the allowed and blocked permissions.
230     * 
231     * @param listName the name of the list to get the allowed and blocked permissions.
232     * @return a list of privacy items under the list listName.
233     * @throws XMPPErrorException 
234     * @throws NoResponseException 
235     * @throws NotConnectedException 
236     */ 
237    private List<PrivacyItem> getPrivacyListItems(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException  {
238        // The request of the list is an privacy message with an empty list
239        Privacy request = new Privacy();
240        request.setPrivacyList(listName, new ArrayList<PrivacyItem>());
241        
242        // Send the package to the server and get the answer
243        Privacy privacyAnswer = getRequest(request);
244        
245        return privacyAnswer.getPrivacyList(listName);
246    }
247
248        /**
249         * Answer the privacy list items under listName with the allowed and blocked permissions.
250         * 
251         * @param listName the name of the list to get the allowed and blocked permissions.
252         * @return a privacy list under the list listName.
253         * @throws XMPPErrorException 
254         * @throws NoResponseException 
255         * @throws NotConnectedException 
256         */ 
257        public PrivacyList getPrivacyList(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException  {
258        return new PrivacyList(false, false, listName, getPrivacyListItems(listName));
259        }
260
261    /**
262     * Answer every privacy list with the allowed and blocked permissions.
263     * 
264     * @return an array of privacy lists.
265     * @throws XMPPErrorException 
266     * @throws NoResponseException 
267     * @throws NotConnectedException 
268     */ 
269    public PrivacyList[] getPrivacyLists() throws NoResponseException, XMPPErrorException, NotConnectedException {
270        Privacy privacyAnswer = this.getPrivacyWithListNames();
271        Set<String> names = privacyAnswer.getPrivacyListNames();
272        PrivacyList[] lists = new PrivacyList[names.size()];
273        boolean isActiveList;
274        boolean isDefaultList;
275        int index=0;
276        for (String listName : names) {
277            isActiveList = listName.equals(privacyAnswer.getActiveName());
278            isDefaultList = listName.equals(privacyAnswer.getDefaultName());
279            lists[index] = new PrivacyList(isActiveList, isDefaultList,
280                    listName, getPrivacyListItems(listName));
281            index = index + 1;
282        }
283        return lists;
284    }
285
286        /**
287         * Set or change the active list to listName.
288         * 
289         * @param listName the list name to set as the active one.
290         * @throws XMPPErrorException 
291         * @throws NoResponseException 
292         * @throws NotConnectedException 
293         */ 
294        public void setActiveListName(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException {
295                // The request of the list is an privacy message with an empty list
296                Privacy request = new Privacy();
297                request.setActiveName(listName);
298                
299                // Send the package to the server
300                setRequest(request);
301        }
302
303        /**
304         * Client declines the use of active lists.
305         * @throws XMPPErrorException 
306         * @throws NoResponseException 
307         * @throws NotConnectedException 
308         */ 
309        public void declineActiveList() throws NoResponseException, XMPPErrorException, NotConnectedException {
310                // The request of the list is an privacy message with an empty list
311                Privacy request = new Privacy();
312                request.setDeclineActiveList(true);
313                
314                // Send the package to the server
315                setRequest(request);
316        }
317
318        /**
319         * Set or change the default list to listName.
320         * 
321         * @param listName the list name to set as the default one.
322         * @throws XMPPErrorException 
323         * @throws NoResponseException 
324         * @throws NotConnectedException 
325         */ 
326        public void setDefaultListName(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException  {
327                // The request of the list is an privacy message with an empty list
328                Privacy request = new Privacy();
329                request.setDefaultName(listName);
330                
331                // Send the package to the server
332                setRequest(request);
333        }
334
335        /**
336         * Client declines the use of default lists.
337         * @throws XMPPErrorException 
338         * @throws NoResponseException 
339         * @throws NotConnectedException 
340         */ 
341        public void declineDefaultList() throws NoResponseException, XMPPErrorException, NotConnectedException {
342                // The request of the list is an privacy message with an empty list
343                Privacy request = new Privacy();
344                request.setDeclineDefaultList(true);
345                
346                // Send the package to the server
347                setRequest(request);
348        }
349
350        /**
351         * The client has created a new list. It send the new one to the server.
352         * 
353     * @param listName the list that has changed its content.
354     * @param privacyItems a List with every privacy item in the list.
355         * @throws XMPPErrorException 
356         * @throws NoResponseException 
357         * @throws NotConnectedException 
358         */ 
359        public void createPrivacyList(String listName, List<PrivacyItem> privacyItems) throws NoResponseException, XMPPErrorException, NotConnectedException  {
360                updatePrivacyList(listName, privacyItems);
361        }
362
363    /**
364     * The client has edited an existing list. It updates the server content with the resulting 
365     * list of privacy items. The {@link PrivacyItem} list MUST contain all elements in the 
366     * list (not the "delta").
367     * 
368     * @param listName the list that has changed its content.
369     * @param privacyItems a List with every privacy item in the list.
370     * @throws XMPPErrorException 
371     * @throws NoResponseException 
372     * @throws NotConnectedException 
373     */ 
374    public void updatePrivacyList(String listName, List<PrivacyItem> privacyItems) throws NoResponseException, XMPPErrorException, NotConnectedException  {
375        // Build the privacy package to add or update the new list
376        Privacy request = new Privacy();
377        request.setPrivacyList(listName, privacyItems);
378
379        // Send the package to the server
380        setRequest(request);
381    }
382
383        /**
384         * Remove a privacy list.
385         * 
386     * @param listName the list that has changed its content.
387         * @throws XMPPErrorException 
388         * @throws NoResponseException 
389         * @throws NotConnectedException 
390         */ 
391        public void deletePrivacyList(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException {
392                // The request of the list is an privacy message with an empty list
393                Privacy request = new Privacy();
394                request.setPrivacyList(listName, new ArrayList<PrivacyItem>());
395
396                // Send the package to the server
397                setRequest(request);
398        }
399
400    /**
401     * Adds a packet listener that will be notified of any new update in the user
402     * privacy communication.
403     *
404     * @param listener a packet listener.
405     */
406    public void addListener(PrivacyListListener listener) {
407        // Keep track of the listener so that we can manually deliver extra
408        // messages to it later if needed.
409        synchronized (listeners) {
410            listeners.add(listener);
411        }
412    }
413
414    /**
415     * Check if the user's server supports privacy lists.
416     * 
417     * @return true, if the server supports privacy lists, false otherwise.
418     * @throws XMPPErrorException 
419     * @throws NoResponseException 
420     * @throws NotConnectedException 
421     */
422    public boolean isSupported() throws NoResponseException, XMPPErrorException, NotConnectedException{
423        return ServiceDiscoveryManager.getInstanceFor(connection()).supportsFeature(
424                        connection().getServiceName(), NAMESPACE);
425    }
426}