Smack/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/PrivacyListManager.java

583 lines
27 KiB
Java

/**
*
* Copyright 2006-2007 Jive Software.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smackx.privacy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.IQResultReplyFilter;
import org.jivesoftware.smack.filter.IQTypeFilter;
import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.filter.StanzaTypeFilter;
import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.privacy.filter.SetActiveListFilter;
import org.jivesoftware.smackx.privacy.filter.SetDefaultListFilter;
import org.jivesoftware.smackx.privacy.packet.Privacy;
import org.jivesoftware.smackx.privacy.packet.PrivacyItem;
/**
* A PrivacyListManager is used by XMPP clients to block or allow communications from other
* users. Use the manager to:
* <ul>
* <li>Retrieve privacy lists.
* <li>Add, remove, and edit privacy lists.
* <li>Set, change, or decline active lists.
* <li>Set, change, or decline the default list (i.e., the list that is active by default).
* </ul>
* Privacy Items can handle different kind of permission communications based on JID, group,
* subscription type or globally (see {@link PrivacyItem}).
*
* @author Francisco Vives
* @see <a href="http://xmpp.org/extensions/xep-0016.html">XEP-16: Privacy Lists</a>
*/
public final class PrivacyListManager extends Manager {
public static final String NAMESPACE = Privacy.NAMESPACE;
public static final StanzaFilter PRIVACY_FILTER = new StanzaTypeFilter(Privacy.class);
private static final StanzaFilter PRIVACY_RESULT = new AndFilter(IQTypeFilter.RESULT, PRIVACY_FILTER);
// Keep the list of instances of this class.
private static final Map<XMPPConnection, PrivacyListManager> INSTANCES = new WeakHashMap<>();
private final Set<PrivacyListListener> listeners = new CopyOnWriteArraySet<>();
static {
// Create a new PrivacyListManager on every established connection.
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
@Override
public void connectionCreated(XMPPConnection connection) {
getInstanceFor(connection);
}
});
}
// TODO implement: private final Map<String, PrivacyList> cachedPrivacyLists = new HashMap<>();
private volatile String cachedActiveListName;
private volatile String cachedDefaultListName;
/**
* Creates a new privacy manager to maintain the communication privacy. Note: no
* information is sent to or received from the server until you attempt to
* get or set the privacy communication.<p>
*
* @param connection the XMPP connection.
*/
private PrivacyListManager(XMPPConnection connection) {
super(connection);
connection.registerIQRequestHandler(new AbstractIqRequestHandler(Privacy.ELEMENT, Privacy.NAMESPACE,
IQ.Type.set, Mode.sync) {
@Override
public IQ handleIQRequest(IQ iqRequest) {
Privacy privacy = (Privacy) iqRequest;
// Notifies the event to the listeners.
for (PrivacyListListener listener : listeners) {
// Notifies the created or updated privacy lists
for (Map.Entry<String, List<PrivacyItem>> entry : privacy.getItemLists().entrySet()) {
String listName = entry.getKey();
List<PrivacyItem> items = entry.getValue();
if (items.isEmpty()) {
listener.updatedPrivacyList(listName);
}
else {
listener.setPrivacyList(listName, items);
}
}
}
return IQ.createResultIQ(privacy);
}
});
// cached(Active|Default)ListName handling
connection.addStanzaSendingListener(new StanzaListener() {
@Override
public void processStanza(Stanza packet) throws NotConnectedException {
XMPPConnection connection = connection();
Privacy privacy = (Privacy) packet;
StanzaFilter iqResultReplyFilter = new IQResultReplyFilter(privacy, connection);
final String activeListName = privacy.getActiveName();
final boolean declinceActiveList = privacy.isDeclineActiveList();
connection.addOneTimeSyncCallback(new StanzaListener() {
@Override
public void processStanza(Stanza packet) throws NotConnectedException {
if (declinceActiveList) {
cachedActiveListName = null;
}
else {
cachedActiveListName = activeListName;
}
return;
}
}, iqResultReplyFilter);
}
}, SetActiveListFilter.INSTANCE);
connection.addStanzaSendingListener(new StanzaListener() {
@Override
public void processStanza(Stanza packet) throws NotConnectedException {
XMPPConnection connection = connection();
Privacy privacy = (Privacy) packet;
StanzaFilter iqResultReplyFilter = new IQResultReplyFilter(privacy, connection);
final String defaultListName = privacy.getDefaultName();
final boolean declinceDefaultList = privacy.isDeclineDefaultList();
connection.addOneTimeSyncCallback(new StanzaListener() {
@Override
public void processStanza(Stanza packet) throws NotConnectedException {
if (declinceDefaultList) {
cachedDefaultListName = null;
}
else {
cachedDefaultListName = defaultListName;
}
return;
}
}, iqResultReplyFilter);
}
}, SetDefaultListFilter.INSTANCE);
connection.addSyncStanzaListener(new StanzaListener() {
@Override
public void processStanza(Stanza packet) throws NotConnectedException {
Privacy privacy = (Privacy) packet;
// If a privacy IQ result stanza has an active or default list name set, then we use that
// as cached list name.
String activeList = privacy.getActiveName();
if (activeList != null) {
cachedActiveListName = activeList;
}
String defaultList = privacy.getDefaultName();
if (defaultList != null) {
cachedDefaultListName = defaultList;
}
}
}, PRIVACY_RESULT);
connection.addConnectionListener(new ConnectionListener() {
@Override
public void authenticated(XMPPConnection connection, boolean resumed) {
// No need to reset the cache if the connection got resumed.
if (resumed) {
return;
}
cachedActiveListName = cachedDefaultListName = null;
}
});
// XEP-0016 § 3.
ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE);
}
/**
* Returns the PrivacyListManager instance associated with a given XMPPConnection.
*
* @param connection the connection used to look for the proper PrivacyListManager.
* @return the PrivacyListManager associated with a given XMPPConnection.
*/
public static synchronized PrivacyListManager getInstanceFor(XMPPConnection connection) {
PrivacyListManager plm = INSTANCES.get(connection);
if (plm == null) {
plm = new PrivacyListManager(connection);
// Register the new instance and associate it with the connection
INSTANCES.put(connection, plm);
}
return plm;
}
/**
* Send the {@link Privacy} stanza to the server in order to know some privacy content and then
* waits for the answer.
*
* @param requestPrivacy is the {@link Privacy} stanza configured properly whose XML
* will be sent to the server.
* @return a new {@link Privacy} with the data received from the server.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
private Privacy getRequest(Privacy requestPrivacy) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
// The request is a get iq type
requestPrivacy.setType(Privacy.Type.get);
return connection().sendIqRequestAndWaitForResponse(requestPrivacy);
}
/**
* Send the {@link Privacy} stanza to the server in order to modify the server privacy and waits
* for the answer.
*
* @param requestPrivacy is the {@link Privacy} stanza configured properly whose xml will be
* sent to the server.
* @return a new {@link Privacy} with the data received from the server.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
private Stanza setRequest(Privacy requestPrivacy) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
// The request is a get iq type
requestPrivacy.setType(Privacy.Type.set);
return connection().sendIqRequestAndWaitForResponse(requestPrivacy);
}
/**
* Answer a privacy containing the list structure without {@link PrivacyItem}.
*
* @return a Privacy with the list names.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
private Privacy getPrivacyWithListNames() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
// The request of the list is an empty privacy message
Privacy request = new Privacy();
// Send the package to the server and get the answer
return getRequest(request);
}
/**
* Answer the active privacy list. Returns <code>null</code> if there is no active list.
*
* @return the privacy list of the active list.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public PrivacyList getActiveList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
Privacy privacyAnswer = this.getPrivacyWithListNames();
String listName = privacyAnswer.getActiveName();
if (StringUtils.isNullOrEmpty(listName)) {
return null;
}
boolean isDefaultAndActive = listName != null && listName.equals(privacyAnswer.getDefaultName());
return new PrivacyList(true, isDefaultAndActive, listName, getPrivacyListItems(listName));
}
/**
* Get the name of the active list.
*
* @return the name of the active list or null if there is none set.
* @throws NoResponseException if there was no response from the remote entity.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
* @since 4.1
*/
public String getActiveListName() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
if (cachedActiveListName != null) {
return cachedActiveListName;
}
return getPrivacyWithListNames().getActiveName();
}
/**
* Answer the default privacy list. Returns <code>null</code> if there is no default list.
*
* @return the privacy list of the default list.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public PrivacyList getDefaultList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
Privacy privacyAnswer = this.getPrivacyWithListNames();
String listName = privacyAnswer.getDefaultName();
if (StringUtils.isNullOrEmpty(listName)) {
return null;
}
boolean isDefaultAndActive = listName.equals(privacyAnswer.getActiveName());
return new PrivacyList(isDefaultAndActive, true, listName, getPrivacyListItems(listName));
}
/**
* Get the name of the default list.
*
* @return the name of the default list or null if there is none set.
* @throws NoResponseException if there was no response from the remote entity.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
* @since 4.1
*/
public String getDefaultListName() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
if (cachedDefaultListName != null) {
return cachedDefaultListName;
}
return getPrivacyWithListNames().getDefaultName();
}
/**
* Returns the name of the effective privacy list.
* <p>
* The effective privacy list is the one that is currently enforced on the connection. It's either the active
* privacy list, or, if the active privacy list is not set, the default privacy list.
* </p>
*
* @return the name of the effective privacy list or null if there is none set.
* @throws NoResponseException if there was no response from the remote entity.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
* @since 4.1
*/
public String getEffectiveListName() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
String activeListName = getActiveListName();
if (activeListName != null) {
return activeListName;
}
return getDefaultListName();
}
/**
* Answer the privacy list items under listName with the allowed and blocked permissions.
*
* @param listName the name of the list to get the allowed and blocked permissions.
* @return a list of privacy items under the list listName.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
private List<PrivacyItem> getPrivacyListItems(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
assert StringUtils.isNotEmpty(listName);
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setPrivacyList(listName, new ArrayList<PrivacyItem>());
// Send the package to the server and get the answer
Privacy privacyAnswer = getRequest(request);
return privacyAnswer.getPrivacyList(listName);
}
/**
* Answer the privacy list items under listName with the allowed and blocked permissions.
*
* @param listName the name of the list to get the allowed and blocked permissions.
* @return a privacy list under the list listName.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public PrivacyList getPrivacyList(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
listName = StringUtils.requireNotNullNorEmpty(listName, "List name must not be null");
return new PrivacyList(false, false, listName, getPrivacyListItems(listName));
}
/**
* Answer every privacy list with the allowed and blocked permissions.
*
* @return an array of privacy lists.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public List<PrivacyList> getPrivacyLists() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
Privacy privacyAnswer = getPrivacyWithListNames();
Set<String> names = privacyAnswer.getPrivacyListNames();
List<PrivacyList> lists = new ArrayList<>(names.size());
for (String listName : names) {
boolean isActiveList = listName.equals(privacyAnswer.getActiveName());
boolean isDefaultList = listName.equals(privacyAnswer.getDefaultName());
lists.add(new PrivacyList(isActiveList, isDefaultList, listName,
getPrivacyListItems(listName)));
}
return lists;
}
/**
* Set or change the active list to listName.
*
* @param listName the list name to set as the active one.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public void setActiveListName(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setActiveName(listName);
// Send the package to the server
setRequest(request);
}
/**
* Client declines the use of active lists.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public void declineActiveList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setDeclineActiveList(true);
// Send the package to the server
setRequest(request);
}
/**
* Set or change the default list to listName.
*
* @param listName the list name to set as the default one.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public void setDefaultListName(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setDefaultName(listName);
// Send the package to the server
setRequest(request);
}
/**
* Client declines the use of default lists.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public void declineDefaultList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setDeclineDefaultList(true);
// Send the package to the server
setRequest(request);
}
/**
* The client has created a new list. It send the new one to the server.
*
* @param listName the list that has changed its content.
* @param privacyItems a List with every privacy item in the list.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public void createPrivacyList(String listName, List<PrivacyItem> privacyItems) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
updatePrivacyList(listName, privacyItems);
}
/**
* The client has edited an existing list. It updates the server content with the resulting
* list of privacy items. The {@link PrivacyItem} list MUST contain all elements in the
* list (not the "delta").
*
* @param listName the list that has changed its content.
* @param privacyItems a List with every privacy item in the list.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public void updatePrivacyList(String listName, List<PrivacyItem> privacyItems) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
// Build the privacy package to add or update the new list
Privacy request = new Privacy();
request.setPrivacyList(listName, privacyItems);
// Send the package to the server
setRequest(request);
}
/**
* Remove a privacy list.
*
* @param listName the list that has changed its content.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public void deletePrivacyList(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
// The request of the list is an privacy message with an empty list
Privacy request = new Privacy();
request.setPrivacyList(listName, new ArrayList<PrivacyItem>());
// Send the package to the server
setRequest(request);
}
/**
* Adds a privacy list listener that will be notified of any new update in the user
* privacy communication.
*
* @param listener a privacy list listener.
* @return true, if the listener was not already added.
*/
public boolean addListener(PrivacyListListener listener) {
return listeners.add(listener);
}
/**
* Removes the privacy list listener.
*
* @param listener TODO javadoc me please
* @return true, if the listener was removed.
*/
public boolean removeListener(PrivacyListListener listener) {
return listeners.remove(listener);
}
/**
* Check if the user's server supports privacy lists.
*
* @return true, if the server supports privacy lists, false otherwise.
* @throws XMPPErrorException if there was an XMPP error returned.
* @throws NoResponseException if there was no response from the remote entity.
* @throws NotConnectedException if the XMPP connection is not connected.
* @throws InterruptedException if the calling thread was interrupted.
*/
public boolean isSupported() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
return ServiceDiscoveryManager.getInstanceFor(connection()).serverSupportsFeature(NAMESPACE);
}
}