diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
index 10515ff7c..677994b65 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
@@ -1371,6 +1371,27 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
sendStanzaWithResponseCallback(iqRequest, replyFilter, callback, exceptionCallback, timeout);
}
+ @Override
+ public void addOneTimeSyncCallback(final PacketListener callback, final PacketFilter packetFilter) {
+ final PacketListener packetListener = new PacketListener() {
+ @Override
+ public void processPacket(Packet packet) throws NotConnectedException {
+ try {
+ callback.processPacket(packet);
+ } finally {
+ removeSyncPacketListener(this);
+ }
+ }
+ };
+ addSyncPacketListener(packetListener, packetFilter);
+ removeCallbacksService.schedule(new Runnable() {
+ @Override
+ public void run() {
+ removeSyncPacketListener(packetListener);
+ }
+ }, getPacketReplyTimeout(), TimeUnit.MILLISECONDS);
+ }
+
private long lastStanzaReceived;
public long getLastStanzaReceived() {
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java
index a8daeccd8..e2b4e64cd 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/XMPPConnection.java
@@ -556,6 +556,15 @@ public interface XMPPConnection {
final ExceptionCallback exceptionCallback, long timeout)
throws NotConnectedException;
+ /**
+ * Add a callback that is called exactly once and synchronously with the incoming stanza that matches the given
+ * packet filter.
+ *
+ * @param callback the callback invoked once the packet filter matches a stanza.
+ * @param packetFilter the filter to match stanzas or null to match all.
+ */
+ public void addOneTimeSyncCallback(PacketListener callback, PacketFilter packetFilter);
+
/**
* Returns the timestamp in milliseconds when the last stanza was received.
*
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/IQResultReplyFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/IQResultReplyFilter.java
new file mode 100644
index 000000000..a9e327b8c
--- /dev/null
+++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/IQResultReplyFilter.java
@@ -0,0 +1,41 @@
+/**
+ *
+ * Copyright 2015 Florian Schmaus
+ *
+ * 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.smack.filter;
+
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ *
+ */
+public class IQResultReplyFilter extends IQReplyFilter {
+
+
+ public IQResultReplyFilter(IQ iqPacket, XMPPConnection conn) {
+ super(iqPacket, conn);
+ }
+
+ @Override
+ public boolean accept(Packet packet) {
+ if (!super.accept(packet)) {
+ return false;
+ }
+ return IQTypeFilter.RESULT.accept(packet);
+ }
+
+}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/PrivacyList.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/PrivacyList.java
index 075a1fc14..47f73a148 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/PrivacyList.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/PrivacyList.java
@@ -70,4 +70,8 @@ public class PrivacyList {
return items;
}
+ @Override
+ public String toString() {
+ return "Privacy List: " + listName + "(active:" + isActiveList + ", default:" + isDefaultList + ")";
+ }
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/PrivacyListManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/PrivacyListManager.java
index ea351f02a..57631a824 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/PrivacyListManager.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/PrivacyListManager.java
@@ -23,6 +23,7 @@ import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
+import org.jivesoftware.smack.AbstractConnectionListener;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPConnection;
@@ -32,25 +33,29 @@ import org.jivesoftware.smack.PacketListener;
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.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
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:
+ * users. Use the manager to:
+ *
* - Retrieve privacy lists.
*
- Add, remove, and edit privacy lists.
*
- Set, change, or decline active lists.
*
- Set, change, or decline the default list (i.e., the list that is active by default).
*
* Privacy Items can handle different kind of permission communications based on JID, group,
- * subscription type or globally (@see PrivacyItem).
+ * subscription type or globally (see {@link PrivacyItem}).
*
* @author Francisco Vives
* @see XEP-16: Privacy Lists
@@ -58,8 +63,11 @@ import org.jivesoftware.smackx.privacy.packet.PrivacyItem;
public class PrivacyListManager extends Manager {
public static final String NAMESPACE = Privacy.NAMESPACE;
- private static final PacketFilter PACKET_FILTER = new AndFilter(IQTypeFilter.SET,
- new PacketTypeFilter(Privacy.class));
+ public static final PacketFilter PRIVACY_FILTER = new PacketTypeFilter(Privacy.class);
+
+ private static final PacketFilter PRIVACY_SET = new AndFilter(IQTypeFilter.SET, PRIVACY_FILTER);
+
+ private static final PacketFilter PRIVACY_RESULT = new AndFilter(IQTypeFilter.RESULT, PRIVACY_FILTER);
// Keep the list of instances of this class.
private static final Map INSTANCES = new WeakHashMap();
@@ -75,6 +83,10 @@ public class PrivacyListManager extends Manager {
});
}
+ // TODO implement: private final Map 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
@@ -82,7 +94,7 @@ public class PrivacyListManager extends Manager {
*
* @param connection the XMPP connection.
*/
- private PrivacyListManager(final XMPPConnection connection) {
+ private PrivacyListManager(XMPPConnection connection) {
super(connection);
connection.addSyncPacketListener(new PacketListener() {
@@ -109,7 +121,78 @@ public class PrivacyListManager extends Manager {
IQ iq = IQ.createResultIQ(privacy);
connection().sendPacket(iq);
}
- }, PACKET_FILTER);
+ }, PRIVACY_SET);
+
+ // cached(Active|Default)ListName handling
+ connection.addPacketSendingListener(new PacketListener() {
+ @Override
+ public void processPacket(Packet packet) throws NotConnectedException {
+ XMPPConnection connection = connection();
+ Privacy privacy = (Privacy) packet;
+ PacketFilter iqResultReplyFilter = new IQResultReplyFilter(privacy, connection);
+ final String activeListName = privacy.getActiveName();
+ final boolean declinceActiveList = privacy.isDeclineActiveList();
+ connection.addOneTimeSyncCallback(new PacketListener() {
+ @Override
+ public void processPacket(Packet packet) throws NotConnectedException {
+ if (declinceActiveList) {
+ cachedActiveListName = null;
+ }
+ else {
+ cachedActiveListName = activeListName;
+ }
+ return;
+ }
+ }, iqResultReplyFilter);
+ }
+ }, SetActiveListFilter.INSTANCE);
+ connection.addPacketSendingListener(new PacketListener() {
+ @Override
+ public void processPacket(Packet packet) throws NotConnectedException {
+ XMPPConnection connection = connection();
+ Privacy privacy = (Privacy) packet;
+ PacketFilter iqResultReplyFilter = new IQResultReplyFilter(privacy, connection);
+ final String defaultListName = privacy.getDefaultName();
+ final boolean declinceDefaultList = privacy.isDeclineDefaultList();
+ connection.addOneTimeSyncCallback(new PacketListener() {
+ @Override
+ public void processPacket(Packet packet) throws NotConnectedException {
+ if (declinceDefaultList) {
+ cachedDefaultListName = null;
+ }
+ else {
+ cachedDefaultListName = defaultListName;
+ }
+ return;
+ }
+ }, iqResultReplyFilter);
+ }
+ }, SetDefaultListFilter.INSTANCE);
+ connection.addSyncPacketListener(new PacketListener() {
+ @Override
+ public void processPacket(Packet 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 AbstractConnectionListener() {
+ @Override
+ public void reconnectionSuccessful() {
+ cachedActiveListName = cachedDefaultListName = null;
+ }
+ });
+
+ // XEP-0016 ยง 3.
+ ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE);
}
/**
@@ -191,13 +274,26 @@ public class PrivacyListManager extends Manager {
public PrivacyList getActiveList() throws NoResponseException, XMPPErrorException, NotConnectedException {
Privacy privacyAnswer = this.getPrivacyWithListNames();
String listName = privacyAnswer.getActiveName();
- boolean isDefaultAndActive = privacyAnswer.getActiveName() != null
- && privacyAnswer.getDefaultName() != null
- && privacyAnswer.getActiveName().equals(
- privacyAnswer.getDefaultName());
+ 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
+ * @throws XMPPErrorException
+ * @throws NotConnectedException
+ * @since 4.1
+ */
+ public String getActiveListName() throws NoResponseException, XMPPErrorException, NotConnectedException {
+ if (cachedActiveListName != null) {
+ return cachedActiveListName;
+ }
+ return getPrivacyWithListNames().getActiveName();
+ }
+
/**
* Answer the default privacy list.
*
@@ -209,13 +305,47 @@ public class PrivacyListManager extends Manager {
public PrivacyList getDefaultList() throws NoResponseException, XMPPErrorException, NotConnectedException {
Privacy privacyAnswer = this.getPrivacyWithListNames();
String listName = privacyAnswer.getDefaultName();
- boolean isDefaultAndActive = privacyAnswer.getActiveName() != null
- && privacyAnswer.getDefaultName() != null
- && privacyAnswer.getActiveName().equals(
- privacyAnswer.getDefaultName());
+ boolean isDefaultAndActive = listName != null && 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
+ * @throws XMPPErrorException
+ * @throws NotConnectedException
+ * @since 4.1
+ */
+ public String getDefaultListName() throws NoResponseException, XMPPErrorException, NotConnectedException {
+ if (cachedDefaultListName != null) {
+ return cachedDefaultListName;
+ }
+ return getPrivacyWithListNames().getDefaultName();
+ }
+
+ /**
+ * Returns the name of the effective privacy list.
+ *
+ * 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.
+ *
+ *
+ * @return the name of the effective privacy list or null if there is none set.
+ * @throws NoResponseException
+ * @throws XMPPErrorException
+ * @throws NotConnectedException
+ * @since 4.1
+ */
+ public String getEffectiveListName() throws NoResponseException, XMPPErrorException, NotConnectedException {
+ String activeListName = getActiveListName();
+ if (activeListName != null) {
+ return activeListName;
+ }
+ return getDefaultListName();
+ }
+
/**
* Answer the privacy list items under listName with the allowed and blocked permissions.
*
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/filter/SetActiveListFilter.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/filter/SetActiveListFilter.java
new file mode 100644
index 000000000..2499cc88c
--- /dev/null
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/filter/SetActiveListFilter.java
@@ -0,0 +1,38 @@
+/**
+ *
+ * Copyright 2015 Florian Schmaus
+ *
+ * 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.filter;
+
+import org.jivesoftware.smack.filter.FlexiblePacketTypeFilter;
+import org.jivesoftware.smack.packet.IQ.Type;
+import org.jivesoftware.smackx.privacy.packet.Privacy;
+
+public class SetActiveListFilter extends FlexiblePacketTypeFilter {
+
+ public static final SetActiveListFilter INSTANCE = new SetActiveListFilter();
+
+ private SetActiveListFilter() {
+ }
+
+ @Override
+ protected boolean acceptSpecific(Privacy privacy) {
+ if (privacy.getType() != Type.set) {
+ return false;
+ }
+ return privacy.getActiveName() != null || privacy.isDeclineActiveList();
+ }
+
+}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/filter/SetDefaultListFilter.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/filter/SetDefaultListFilter.java
new file mode 100644
index 000000000..cac736902
--- /dev/null
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/filter/SetDefaultListFilter.java
@@ -0,0 +1,38 @@
+/**
+ *
+ * Copyright 2015 Florian Schmaus
+ *
+ * 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.filter;
+
+import org.jivesoftware.smack.filter.FlexiblePacketTypeFilter;
+import org.jivesoftware.smack.packet.IQ.Type;
+import org.jivesoftware.smackx.privacy.packet.Privacy;
+
+public class SetDefaultListFilter extends FlexiblePacketTypeFilter {
+
+ public static final SetDefaultListFilter INSTANCE = new SetDefaultListFilter();
+
+ private SetDefaultListFilter() {
+ }
+
+ @Override
+ protected boolean acceptSpecific(Privacy privacy) {
+ if (privacy.getType() != Type.set) {
+ return false;
+ }
+ return privacy.getDefaultName() != null || privacy.isDeclineDefaultList();
+ }
+
+}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/packet/Privacy.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/packet/Privacy.java
index 3de3faf29..0188cf236 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/packet/Privacy.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/privacy/packet/Privacy.java
@@ -292,7 +292,7 @@ public class Privacy extends IQ {
buf.append("");
} else {
if (this.getActiveName() != null) {
- buf.append("");
+ buf.append("");
}
}
// Add the default tag
@@ -300,7 +300,7 @@ public class Privacy extends IQ {
buf.append("");
} else {
if (this.getDefaultName() != null) {
- buf.append("");
+ buf.append("");
}
}
@@ -310,9 +310,9 @@ public class Privacy extends IQ {
List items = entry.getValue();
// Begin the list tag
if (items.isEmpty()) {
- buf.append("");
+ buf.append("");
} else {
- buf.append("");
+ buf.append("");
}
for (PrivacyItem item : items) {
// Append the item xml representation