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}