From 929361f35b71b1993dddb69eb60c06533d77e319 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 12 May 2018 19:48:15 +0200 Subject: [PATCH 01/23] Smack 4.3.0-rc1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 000dc59cb..cb120112e 100644 --- a/version.gradle +++ b/version.gradle @@ -1,7 +1,7 @@ allprojects { ext { - shortVersion = '4.3.0-beta2' - isSnapshot = false + shortVersion = '4.3.0-rc1' + isSnapshot = true jxmppVersion = '0.6.2' miniDnsVersion = '0.3.0' smackMinAndroidSdk = 9 From 98a029e9c7c2945cfaf18f6b17a12e4d9020ab8b Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 13 May 2018 19:37:41 +0200 Subject: [PATCH 02/23] Set name of local SOCKS5 proxy thread --- .../org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java | 1 + 1 file changed, 1 insertion(+) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java index e97ea15e6..b39527ee3 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java @@ -210,6 +210,7 @@ public final class Socks5Proxy { if (this.serverSocket != null) { this.serverThread = new Thread(this.serverProcess); + this.serverThread.setName("Smack Local SOCKS5 Proxy"); this.serverThread.start(); } } From fd5e86ce5af8d72de60ba6caf71a159dcce8b223 Mon Sep 17 00:00:00 2001 From: Boris Grozev Date: Mon, 21 May 2018 14:44:23 -0500 Subject: [PATCH 03/23] fix: Cleans the multiUserChats map. --- .../smack/util/CleaningWeakReferenceMap.java | 94 +++++++++++++++++++ .../smackx/muc/MultiUserChatManager.java | 4 +- 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/util/CleaningWeakReferenceMap.java diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/CleaningWeakReferenceMap.java b/smack-core/src/main/java/org/jivesoftware/smack/util/CleaningWeakReferenceMap.java new file mode 100644 index 000000000..fb16de964 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/CleaningWeakReferenceMap.java @@ -0,0 +1,94 @@ +/** + * + * Copyright the original author or authors + * + * 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.util; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Iterator; + +/** + * Extends a {@link HashMap} with {@link WeakReference} values, so that + * weak references which have been cleared are periodically removed from + * the map. The cleaning occurs as part of {@link #put}, after a specific + * number ({@link #cleanInterval}) of calls to {@link #put}. + * + * @param The key type. + * @param The value type. + * + * @author Boris Grozev + */ +public class CleaningWeakReferenceMap + extends HashMap> { + private static final long serialVersionUID = 0L; + + /** + * The number of calls to {@link #put} after which to clean this map + * (i.e. remove cleared {@link WeakReference}s from it). + */ + private final int cleanInterval; + + /** + * The number of times {@link #put} has been called on this instance + * since the last time it was {@link #clean}ed. + */ + private int numberOfInsertsSinceLastClean = 0; + + /** + * Initializes a new {@link CleaningWeakReferenceMap} instance with the + * default clean interval. + */ + public CleaningWeakReferenceMap() { + this(50); + } + + /** + * Initializes a new {@link CleaningWeakReferenceMap} instance with a given + * clean interval. + * @param cleanInterval the number of calls to {@link #put} after which the + * map will clean itself. + */ + public CleaningWeakReferenceMap(int cleanInterval) { + this.cleanInterval = cleanInterval; + } + + @Override + public WeakReference put(K key, WeakReference value) { + WeakReference ret = super.put(key, value); + + if (numberOfInsertsSinceLastClean++ > cleanInterval) { + numberOfInsertsSinceLastClean = 0; + clean(); + } + + return ret; + } + + /** + * Removes all cleared entries from this map (i.e. entries whose value + * is a cleared {@link WeakReference}). + */ + private void clean() { + Iterator>> iter = entrySet().iterator(); + while (iter.hasNext()) { + Entry> e = iter.next(); + if (e != null && e.getValue() != null + && e.getValue().get() == null) { + iter.remove(); + } + } + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java index a627ada6a..8c5a98ccd 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java @@ -19,7 +19,6 @@ package org.jivesoftware.smackx.muc; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -46,6 +45,7 @@ import org.jivesoftware.smack.filter.StanzaTypeFilter; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.util.Async; +import org.jivesoftware.smack.util.CleaningWeakReferenceMap; import org.jivesoftware.smackx.disco.AbstractNodeInformationProvider; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; @@ -144,7 +144,7 @@ public final class MultiUserChatManager extends Manager { * those instances to get garbage collected. Note that MultiUserChat instances can not get garbage collected while * the user is joined, because then the MUC will have PacketListeners added to the XMPPConnection. */ - private final Map> multiUserChats = new HashMap<>(); + private final Map> multiUserChats = new CleaningWeakReferenceMap<>(); private boolean autoJoinOnReconnect; From 7a2e4140c7e96ac556f5c61860325ad50727e294 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 31 May 2018 18:27:11 +0200 Subject: [PATCH 04/23] Add MamManager.getArchiveAddress() --- .../jivesoftware/smackx/mam/MamManager.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java index 04c3d3f32..2e0251b92 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java @@ -112,6 +112,24 @@ public final class MamManager extends Manager { serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); } + /** + * The the XMPP address of this MAM archive. Note that this method may return {@code null} if this MamManager + * handles the local entity's archive and if the connection has never been authenticated at least once. + * + * @return the XMPP address of this MAM archive or {@code null}. + * @since 4.3.0 + */ + public Jid getArchiveAddress() { + if (archiveAddress == null) { + EntityFullJid localJid = connection().getUser(); + if (localJid == null) { + return null; + } + return localJid.asBareJid(); + } + return archiveAddress; + } + /** * Query archive with a maximum amount of results. * From 50e98fe31d1ed17db6a72b95c4c32157fc33154e Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 31 May 2018 18:27:34 +0200 Subject: [PATCH 05/23] Improve javadoc for MamManager.isSupported() --- .../src/main/java/org/jivesoftware/smackx/mam/MamManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java index 2e0251b92..710553178 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java @@ -609,9 +609,9 @@ public final class MamManager extends Manager { } /** - * Check if MAM is supported for the XMPP connection managed by this MamManager. + * Check if this MamManager's archive address supports MAM. * - * @return true if MAM is supported for the XMPP connection, falseotherwhise. + * @return true if MAM is supported, falseotherwise. * * @throws NoResponseException * @throws XMPPErrorException From cb97b5032cf86732843ebe203f137a6f512092b1 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 31 May 2018 18:27:54 +0200 Subject: [PATCH 06/23] Improve javadoc for MamManager.getInstanceFor(XMPPConnection) --- .../src/main/java/org/jivesoftware/smackx/mam/MamManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java index 710553178..7c2addf9f 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java @@ -79,9 +79,9 @@ public final class MamManager extends Manager { private static final Map> INSTANCES = new WeakHashMap<>(); /** - * Get the singleton instance of MamManager. + * Get a MamManager for the MAM archive of the local entity (the "user") of the given connection. * - * @param connection + * @param connection the XMPP connection to get the archive for. * @return the instance of MamManager */ public static MamManager getInstanceFor(XMPPConnection connection) { From ccf2b8bcf8f01cb0341958bcb9340093b18a3b59 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 31 May 2018 18:37:08 +0200 Subject: [PATCH 07/23] Add MultiUserChat.getXmppConnection() --- .../org/jivesoftware/smackx/muc/MultiUserChat.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index c22995cd6..f2f2488b8 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -2442,6 +2442,16 @@ public class MultiUserChat { } } + /** + * Get the XMPP connection associated with this chat instance. + * + * @return the associated XMPP connection. + * @since 4.3.0 + */ + public XMPPConnection getXmppConnection() { + return connection; + } + @Override public String toString() { return "MUC: " + room + "(" + connection.getUser() + ")"; From fb5d7ff7d4077a16782579608958c64ef7ab6fb9 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 31 May 2018 18:37:31 +0200 Subject: [PATCH 08/23] Add MamManager.getInstanceFor(MultiUserChat) --- .../jivesoftware/smackx/mam/MamManager.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java index 7c2addf9f..300e78f1a 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2016-2017 Florian Schmaus, Fernando Ramirez + * Copyright © 2017-2018 Florian Schmaus, 2016-2017 Fernando Ramirez * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,6 +46,7 @@ import org.jivesoftware.smackx.mam.element.MamPrefsIQ; import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior; import org.jivesoftware.smackx.mam.element.MamQueryIQ; import org.jivesoftware.smackx.mam.filter.MamResultFilter; +import org.jivesoftware.smackx.muc.MultiUserChat; import org.jivesoftware.smackx.rsm.packet.RSMSet; import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.packet.DataForm; @@ -82,10 +83,24 @@ public final class MamManager extends Manager { * Get a MamManager for the MAM archive of the local entity (the "user") of the given connection. * * @param connection the XMPP connection to get the archive for. - * @return the instance of MamManager + * @return the instance of MamManager. */ public static MamManager getInstanceFor(XMPPConnection connection) { - return getInstanceFor(connection, null); + return getInstanceFor(connection, (Jid) null); + } + + /** + * Get a MamManager for the MAM archive of the given {@code MultiUserChat}. Note that not all MUCs support MAM, + * hence it is recommended to use {@link #isSupported()} to check if MAM is supported by the MUC. + * + * @param multiUserChat the MultiUserChat to retrieve the MamManager for. + * @return the MamManager for the given MultiUserChat. + * @since 4.3.0 + */ + public static MamManager getInstanceFor(MultiUserChat multiUserChat) { + XMPPConnection connection = multiUserChat.getXmppConnection(); + Jid archiveAddress = multiUserChat.getRoom(); + return getInstanceFor(connection, archiveAddress); } public static synchronized MamManager getInstanceFor(XMPPConnection connection, Jid archiveAddress) { From 2adf8a79af102a133930adf99bdded41cfa7aff3 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 3 Jun 2018 17:01:08 +0200 Subject: [PATCH 09/23] Add CommandsProviderTest --- .../provider/CommandsProviderTest.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 smack-extensions/src/test/java/org/jivesoftware/smackx/commands/provider/CommandsProviderTest.java diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/commands/provider/CommandsProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/commands/provider/CommandsProviderTest.java new file mode 100644 index 000000000..7da9edac6 --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/commands/provider/CommandsProviderTest.java @@ -0,0 +1,50 @@ +/** + * + * Copyright 2018 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.commands.provider; + +import static org.junit.Assert.assertEquals; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.packet.StanzaError; +import org.jivesoftware.smack.util.PacketParserUtils; + +import org.jivesoftware.smackx.commands.AdHocCommand; +import org.jivesoftware.smackx.commands.packet.AdHocCommandData; + +import org.junit.Test; + +public class CommandsProviderTest { + + @Test + public void parseErrorWithRequest() throws Exception { + final String errorWithRequest = "" + + "" + + "" + "" + + "" + "" + ""; + + final Stanza requestStanza = PacketParserUtils.parseStanza(errorWithRequest); + final AdHocCommandData adHocIq = (AdHocCommandData) requestStanza; + + assertEquals(IQ.Type.error, adHocIq.getType()); + assertEquals(AdHocCommand.Action.execute, adHocIq.getAction()); + + StanzaError error = adHocIq.getError(); + assertEquals(StanzaError.Type.CANCEL, error.getType()); + assertEquals(StanzaError.Condition.bad_request, error.getCondition()); + } +} From 298bcc80973d2e41773c77919a1ee42300083808 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 3 Jun 2018 17:17:29 +0200 Subject: [PATCH 10/23] Improve FormField.resetValues() by using clear() instead of removeAll(). --- .../src/main/java/org/jivesoftware/smackx/xdata/FormField.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java index d2489b5c7..6c09b6086 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java @@ -378,7 +378,7 @@ public class FormField implements NamedElement { */ protected void resetValues() { synchronized (values) { - values.removeAll(new ArrayList<>(values)); + values.clear(); } } From d26baeb66fd3161e178383996b52e84f5bf78cbc Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 3 Jun 2018 17:28:49 +0200 Subject: [PATCH 11/23] Add Date API methods to FormField values --- .../jivesoftware/smackx/xdata/FormField.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java index 6c09b6086..b45c139bc 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java @@ -17,8 +17,10 @@ package org.jivesoftware.smackx.xdata; +import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.List; import org.jivesoftware.smack.packet.NamedElement; @@ -27,6 +29,8 @@ import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smackx.xdatavalidation.packet.ValidateElement; +import org.jxmpp.util.XmppDateTime; + /** * Represents a field of a form. The field could be used to represent a question to complete, * a completed question or a data returned from a search. The exact interpretation of the field @@ -266,6 +270,21 @@ public class FormField implements NamedElement { return firstValue.toString(); } + /** + * Parses the first value of this form field as XEP-0082 date/time format and returns a date instance or {@code null}. + * + * @return a Date instance representing the date/time information of the first value of this field. + * @throws ParseException if parsing fails. + * @since 4.3.0 + */ + public Date getFirstValueAsDate() throws ParseException { + String valueString = getFirstValue(); + if (valueString == null) { + return null; + } + return XmppDateTime.parseXEP0082Date(valueString); + } + /** * Returns the variable name that the question is filling out. *

@@ -361,6 +380,18 @@ public class FormField implements NamedElement { } } + /** + * Adds the given Date as XEP-0082 formated string by invoking {@link #addValue(CharSequence)} after the date + * instance was formated. + * + * @param date the date instance to add as XEP-0082 formated string. + * @since 4.3.0 + */ + public void addValue(Date date) { + String dateString = XmppDateTime.formatXEP0082Date(date); + addValue(dateString); + } + /** * Adds a default values to the question if the question is part of a form to fill out. * Otherwise, adds an answered values to the question. From 8aa7029b385b88b1b26c2b70dc7c7c367163e39c Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 3 Jun 2018 21:19:04 +0200 Subject: [PATCH 12/23] Add DataForm.addFields() --- .../smackx/xdata/packet/DataForm.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/packet/DataForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/packet/DataForm.java index 5315c6d49..326a3a248 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/packet/DataForm.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/packet/DataForm.java @@ -18,6 +18,7 @@ package org.jivesoftware.smackx.xdata.packet; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; @@ -234,6 +235,26 @@ public class DataForm implements ExtensionElement { } } + /** + * Add the given fields to this form. + * + * @param fieldsToAdd + * @return true if a field was overridden. + * @since 4.3.0 + */ + public boolean addFields(Collection fieldsToAdd) { + boolean fieldOverridden = false; + synchronized (fields) { + for (FormField field : fieldsToAdd) { + FormField previousField = fields.put(field.getVariable(), field); + if (previousField != null) { + fieldOverridden = true; + } + } + } + return fieldOverridden; + } + /** * Adds a new instruction to the list of instructions that explain how to fill out the form * and what the form is about. The dataform could include multiple instructions since each From f2ea3e0d5bf522a5705125ee7698eb9b00f7f0b0 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 3 Jun 2018 21:19:38 +0200 Subject: [PATCH 13/23] Add XmlUnitUtils.assertXmlSimilar() --- .../org/jivesoftware/smack/test/util/XmlUnitUtils.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/smack-core/src/test/java/org/jivesoftware/smack/test/util/XmlUnitUtils.java b/smack-core/src/test/java/org/jivesoftware/smack/test/util/XmlUnitUtils.java index 7a25fe455..dfde2efc7 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/test/util/XmlUnitUtils.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/test/util/XmlUnitUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2014 Florian Schmaus + * Copyright 2014-2018 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,10 +26,14 @@ import org.xml.sax.SAXException; public class XmlUnitUtils { + // TOOD: Remove this method. public static void assertSimilar(CharSequence expected, CharSequence actual) throws SAXException, IOException { + assertXmlSimilar(expected, actual); + } + + public static void assertXmlSimilar(CharSequence expected, CharSequence actual) throws SAXException, IOException { Diff diff = new Diff(expected.toString(), actual.toString()); diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier()); assertXMLEqual(diff, true); } - } From 5ae164f670c45af402232386120d7072683a379e Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 6 Jun 2018 08:39:09 +0200 Subject: [PATCH 14/23] Add Forwarded.extractMessagesFrom(Collection) --- .../smackx/forward/packet/Forwarded.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/forward/packet/Forwarded.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/forward/packet/Forwarded.java index 5aec4388e..edc1fbd13 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/forward/packet/Forwarded.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/forward/packet/Forwarded.java @@ -16,7 +16,12 @@ */ package org.jivesoftware.smackx.forward.packet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -112,4 +117,21 @@ public class Forwarded implements ExtensionElement { public static Forwarded from(Stanza packet) { return packet.getExtension(ELEMENT, NAMESPACE); } + + /** + * Extract messages in a collection of forwarded elements. Note that it is required that the {@link Forwarded} in + * the given collection only contain {@link Message} stanzas. + * + * @param forwardedCollection the collection to extract from. + * @return a list a the extracted messages. + * @since 4.3.0 + */ + public static List extractMessagesFrom(Collection forwardedCollection) { + List res = new ArrayList<>(forwardedCollection.size()); + for (Forwarded forwarded : forwardedCollection) { + Message message = (Message) forwarded.forwardedPacket; + res.add(message); + } + return res; + } } From a3cf1ab0ca0f5ed4ab5b54fa5345a11a30a47f24 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 6 Jun 2018 08:39:44 +0200 Subject: [PATCH 15/23] Fix MamManager.isSupported(): Use archive address --- .../main/java/org/jivesoftware/smackx/mam/MamManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java index 300e78f1a..762bdaa9e 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java @@ -51,7 +51,6 @@ import org.jivesoftware.smackx.rsm.packet.RSMSet; import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.packet.DataForm; -import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.Jid; @@ -636,8 +635,9 @@ public final class MamManager extends Manager { * @see XEP-0313 § 7. Determining support */ public boolean isSupported() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - BareJid myBareJid = connection().getUser().asBareJid(); - return serviceDiscoveryManager.supportsFeature(myBareJid, MamElements.NAMESPACE); + // Note that this may return 'null' but SDM's supportsFeature() does the right thing™ then. + Jid archiveAddress = getArchiveAddress(); + return serviceDiscoveryManager.supportsFeature(archiveAddress, MamElements.NAMESPACE); } private static DataForm getNewMamForm() { From 414d730962f6a2ab0d5f31ef39a3e16b0d553cb3 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 7 Jun 2018 17:15:16 +0200 Subject: [PATCH 16/23] Optimize XmlStringBuilder.element() for the empty element case For example RSM (XEP-0059) gives a different semantic as UID. --- .../java/org/jivesoftware/smack/util/XmlStringBuilder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java index 0fdaceeb8..3f60077f4 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java @@ -75,7 +75,9 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element { * @return the XmlStringBuilder */ public XmlStringBuilder element(String name, String content) { - assert content != null; + if (content.isEmpty()) { + return emptyElement(name); + } openElement(name); escape(content); closeElement(name); From c792be92675803684477f7ff235baaf74ef85cfd Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 7 Jun 2018 17:55:00 +0200 Subject: [PATCH 17/23] Add StringUtils.requireNullOrNotEmpty() --- .../java/org/jivesoftware/smack/util/StringUtils.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java index 4b9bfe94c..c2650ebcc 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java @@ -451,6 +451,16 @@ public class StringUtils { return cs; } + public static CS requireNullOrNotEmpty(CS cs, String message) { + if (cs == null) { + return null; + } + if (cs.toString().isEmpty()) { + throw new IllegalArgumentException(message); + } + return cs; + } + /** * Return the String representation of the given char sequence if it is not null. * From 77707737df4f25c45b66bfac80f0b1895af686b0 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 11 Jun 2018 18:24:15 +0200 Subject: [PATCH 18/23] Make StanzaCollector's cancelled field volatile --- .../src/main/java/org/jivesoftware/smack/StanzaCollector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java b/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java index 5c6ed9784..202ef25a1 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2016-2017 Florian Schmaus. + * Copyright 2003-2007 Jive Software, 2016-2018 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,7 +55,7 @@ public class StanzaCollector { private final Stanza request; - private boolean cancelled = false; + private volatile boolean cancelled = false; /** * Creates a new stanza collector. If the stanza filter is null, then From 1dec29617e025187740ddbd3f5d286107df1747a Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 11 Jun 2018 18:24:37 +0200 Subject: [PATCH 19/23] Fix StanzaCollector's Exception message: s/Packet/Stanza/ --- .../src/main/java/org/jivesoftware/smack/StanzaCollector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java b/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java index 202ef25a1..cec4b3c95 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java @@ -300,7 +300,7 @@ public class StanzaCollector { private void throwIfCancelled() { if (cancelled) { - throw new IllegalStateException("Packet collector already cancelled"); + throw new IllegalStateException("Stanza collector already cancelled"); } } From d958b42effe6adf8cd33b8c808f7ef6f98b5a846 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 11 Jun 2018 18:25:07 +0200 Subject: [PATCH 20/23] Add StanzaCollector.getCollectedStanzasAfterCancelled() --- .../jivesoftware/smack/StanzaCollector.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java b/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java index cec4b3c95..0b933909e 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/StanzaCollector.java @@ -17,6 +17,8 @@ package org.jivesoftware.smack; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; @@ -268,6 +270,27 @@ public class StanzaCollector { return result; } + private List collectedCache; + + /** + * Return a list of all collected stanzas. This method must be invoked after the collector has been cancelled. + * + * @return a list of collected stanzas. + * @since 4.3.0 + */ + public List getCollectedStanzasAfterCancelled() { + if (!cancelled) { + throw new IllegalStateException("Stanza collector was not yet cancelled"); + } + + if (collectedCache == null) { + collectedCache = new ArrayList<>(getCollectedCount()); + resultQueue.drainTo(collectedCache); + } + + return collectedCache; + } + /** * Get the number of collected stanzas this stanza collector has collected so far. * From 9161ba9e7d4f603eb03e7d101f76c8eaae46fa10 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 9 Jun 2018 15:03:14 +0200 Subject: [PATCH 21/23] Improve MamManager API --- documentation/extensions/mam.md | 190 +---- .../jivesoftware/smackx/mam/MamManager.java | 687 ++++++++++++++++-- .../jivesoftware/smackx/mam/FiltersTest.java | 105 +-- .../smackx/mam/ResultsLimitTest.java | 11 +- .../smackx/mam/RetrieveFormFieldsTest.java | 30 +- .../smackx/mam/MamIntegrationTest.java | 144 +++- .../smackx/omemo/OmemoManager.java | 6 +- .../smackx/omemo/OmemoService.java | 13 +- 8 files changed, 794 insertions(+), 392 deletions(-) diff --git a/documentation/extensions/mam.md b/documentation/extensions/mam.md index 868163b16..012132c58 100644 --- a/documentation/extensions/mam.md +++ b/documentation/extensions/mam.md @@ -3,192 +3,4 @@ Message Archive Management [Back](index.md) -Query and control an archive of messages stored on a server. - - * Check MAM support - * Query archive - * Paging - * Get form fields - * Get preferences - * Update preferences - - -**XEP related:** [XEP-0313](http://xmpp.org/extensions/xep-0313.html) - - -Get an instance of Message Archive Management Manager ------------------------------------------------------ - -``` -MamManager mamManager = MamManager.getInstanceFor(connection); -``` - - -Check MAM support ------------------ - -``` -boolean isSupported = mamManager.isSupported(); -``` - - -Query archive -------------- - -``` -MamQueryResult mamQueryResult = mamManager.queryArchive(max); -``` -*max* is an `Integer` - -or - -``` -MamQueryResult mamQueryResult = mamManager.queryArchive(withJid); -``` -*withJid* is a `Jid` - -or - -``` -MamQueryResult mamQueryResult = mamManager.queryArchive(start, end); -``` -*start* is a `Date` - -*end* is a `Date` - -or - -``` -MamQueryResult mamQueryResult = mamManager.queryArchive(additionalFields); -``` -*additionalFields* is a `List` - -or - -``` -MamQueryResult mamQueryResult = mamManager.queryArchiveWithStartDate(start); -``` -*start* is a `Date` - -or - -``` -MamQueryResult mamQueryResult = mamManager.queryArchiveWithEndDate(end); -``` -*end* is a `Date` - -or - -``` -MamQueryResult mamQueryResult = mamManager.queryArchive(max, start, end, withJid, additionalFields); -``` -*max* is an `Integer` - -*start* is a `Date` - -*end* is a `Date` - -*withJid* is a `Jid` - -*additionalFields* is a `List` - - -**Get data from mamQueryResult object** - -``` -// Get forwarded messages -List forwardedMessages = mamQueryResult.forwardedMessages; - -// Get fin IQ -MamFinIQ mamFinIQ = mamQueryResult.mamFin; -``` - - -Paging ------- - -**Get a page** - -``` -MamQueryResult mamQueryResult = mamManager.page(dataForm, rsmSet); -``` -*dataForm* is a `DataForm` - -*rsmSet* is a `RSMSet` - - -**Get the next page** - -``` -MamQueryResult mamQueryResult = mamManager.pageNext(previousMamQueryResult, count); -``` -*previousMamQueryResult* is a `MamQueryResult` - -*count* is an `int` - - -**Get page before the first message saved (specific chat)** - -``` -MamQueryResult mamQueryResult = mamManager.pageBefore(chatJid, firstMessageId, max); -``` -*chatJid* is a `Jid` - -*firstMessageId* is a `String` - -*max* is an `int` - - -**Get page after the last message saved (specific chat)** - -``` -MamQueryResult mamQueryResult = mamManager.pageAfter(chatJid, lastMessageId, max); -``` -*chatJid* is a `Jid` - -*lastMessageId* is a `String` - -*max* is an `int` - - -Get form fields ---------------- - -``` -List formFields = mamManager.retrieveFormFields(); -``` - - -Get preferences ---------------- - -``` -MamPrefsResult mamPrefsResult = mamManager.retrieveArchivingPreferences(); - -// Get preferences IQ -MamPrefsIQ mamPrefs = mamPrefsResult.mamPrefs; - -// Obtain always and never list -List alwaysJids = mamPrefs.getAlwaysJids(); -List neverJids = mamPrefs.getNeverJids(); - -// Obtain default behaviour (can be 'always', 'never' or 'roster') -DefaultBehavior defaultBehavior = mamPrefs.getDefault(); - -// Get the data form -DataForm dataForm = mamPrefsResult.form; -``` - - -Update preferences ------------------- - -``` -MamPrefsResult mamPrefsResult = mamManager.updateArchivingPreferences(alwaysJids, neverJids, defaultBehavior); -``` -*alwaysJids* is a `List` - -*neverJids* is a `List` - -*defaultBehavior* is a `DefaultBehavior` - +See the javadoc of `MamManager` for details. diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java index 762bdaa9e..45496060d 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java @@ -16,7 +16,9 @@ */ package org.jivesoftware.smackx.mam; +import java.text.ParseException; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -36,11 +38,14 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.filter.IQReplyFilter; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.forward.packet.Forwarded; import org.jivesoftware.smackx.mam.element.MamElements; +import org.jivesoftware.smackx.mam.element.MamElements.MamResultExtension; import org.jivesoftware.smackx.mam.element.MamFinIQ; import org.jivesoftware.smackx.mam.element.MamPrefsIQ; import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior; @@ -54,10 +59,95 @@ import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.Jid; -import org.jxmpp.util.XmppDateTime; /** - * A Manager for Message Archive Management (XEP-0313). + * A Manager for Message Archive Management (MAM, XEP-0313). + * + *

Get an instance of a manager for a message archive

+ * + * In order to work with {@link MamManager} you need to obtain an instance for a particular archive. + * To get the instance for the default archive on the user's server, use the {@link #getInstanceFor(XMPPConnection)} method. + * + *
+ * {@code
+ * XMPPConnection connection = ...
+ * MamManager mamManager = MamManager.getInstanceFor(connection);
+ * }
+ * 
+ * + * If you want to retrieve a manager for a different archive use {@link #getInstanceFor(XMPPConnection, Jid)}, which takes the archive's XMPP address as second argument. + * + *

Check if MAM is supported

+ * + * After you got your manager instance, you probably first want to check if MAM is supported. + * Simply use {@link #isSupported()} to check if there is a MAM archive available. + * + *
+ * {@code
+ * boolean isSupported = mamManager.isSupported();
+ * }
+ * 
+ * + *

Message Archive Preferences

+ * + * After you have verified that the MAM is supported, you probably want to configure the archive first before using it. + * One of the most important preference is to enable MAM for your account. + * Some servers set up new accounts with MAM disabled by default. + * You can do so by calling {@link #enableMamForAllMessages()}. + * + *

Retrieve current preferences

+ * + * The archive's preferences can be retrieved using {@link #retrieveArchivingPreferences()}. + * + *

Update preferences

+ * + * Use {@link MamPrefsResult#asMamPrefs()} to get a modifiable {@link MamPrefs} instance. + * After performing the desired changes, use {@link #updateArchivingPreferences(MamPrefs)} to update the preferences. + * + *

Query the message archive

+ * + * Querying a message archive involves a two step process. First you need specify the query's arguments, for example a date range. + * The query arguments of a particular query are represented by a {@link MamQueryArgs} instance, which can be build using {@link MamQueryArgs.Builder}. + * + * After you have build such an instance, use {@link #queryArchive(MamQueryArgs)} to issue the query. + * + *
+ * {@code
+ * MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
+ *                                 .withJid(jid)
+ *                                 .setResultPageSize(10)
+ *                                 .queryRecentPage()
+ *                                 .build();
+ * MamQuery mamQuery = mamManager.queryArchive(mamQueryArgs);
+ * }
+ * 
+ * + * On success {@link #queryArchive(MamQueryArgs)} returns a {@link MamQuery} instance. + * The instance will hold one page of the queries result set. + * Use {@link MamQuery#getMessages()} to retrieve the messages of the archive belonging to the page. + * + * You can get the whole page including all metadata using {@link MamQuery#getPage()}. + * + *

Paging through the results

+ * + * Because the matching result set could be potentially very big, a MAM service will probably not return all matching messages. + * Instead the results are possibly send in multiple pages. + * To check if the result was complete or if there are further pages, use {@link MamQuery#isComplete()}. + * If this method returns {@code false}, then you may want to page through the archive. + * + * {@link MamQuery} provides convince methods to do so: {@link MamQuery#pageNext(int)} and {@link MamQuery#pagePrevious(int)}. + * + *
+ * {@code
+ * MamQuery nextPageMamQuery = mamQuery.pageNext(10);
+ * }
+ * 
+ * + *

Get the supported form fields

+ * + * You can use {@link #retrieveFormFields()} to retrieve a list of the supported additional form fields by this archive. + * Those fields can be used for further restrict a query. + * * * @see XEP-0313: Message * Archive Management @@ -76,6 +166,10 @@ public final class MamManager extends Manager { }); } + private static final String FORM_FIELD_WITH = "with"; + private static final String FORM_FIELD_START = "start"; + private static final String FORM_FIELD_END = "end"; + private static final Map> INSTANCES = new WeakHashMap<>(); /** @@ -144,6 +238,213 @@ public final class MamManager extends Manager { return archiveAddress; } + public static final class MamQueryArgs { + private final String node; + + private final Map formFields; + + private final Integer maxResults; + + private final String afterUid; + + private final String beforeUid; + + private MamQueryArgs(Builder builder) { + node = builder.node; + formFields = builder.formFields; + if (builder.maxResults > 0) { + maxResults = builder.maxResults; + } else { + maxResults = null; + } + afterUid = builder.afterUid; + beforeUid = builder.beforeUid; + } + + private DataForm dataForm; + + DataForm getDataForm() { + if (dataForm != null) { + return dataForm; + } + dataForm = getNewMamForm(); + dataForm.addFields(formFields.values()); + return dataForm; + } + + void maybeAddRsmSet(MamQueryIQ mamQueryIQ) { + if (maxResults == null && afterUid == null && beforeUid == null) { + return; + } + + int max; + if (maxResults != null) { + max = maxResults; + } else { + max = -1; + } + + RSMSet rsmSet = new RSMSet(afterUid, beforeUid, -1, -1, null, max, null, -1); + mamQueryIQ.addExtension(rsmSet); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String node; + + private final Map formFields = new HashMap<>(8); + + private int maxResults = -1; + + private String afterUid; + + private String beforeUid; + + public Builder queryNode(String node) { + if (node == null) { + return this; + } + + this.node = node; + + return this; + } + + public Builder limitResultsToJid(Jid withJid) { + if (withJid == null) { + return this; + } + + FormField formField = getWithFormField(withJid); + formFields.put(formField.getVariable(), formField); + + return this; + } + + public Builder limitResultsSince(Date start) { + if (start == null) { + return this; + } + + FormField formField = new FormField(FORM_FIELD_START); + formField.addValue(start); + formFields.put(formField.getVariable(), formField); + + FormField endFormField = formFields.get(FORM_FIELD_END); + if (endFormField != null) { + Date end; + try { + end = endFormField.getFirstValueAsDate(); + } + catch (ParseException e) { + throw new IllegalStateException(e); + } + if (end.getTime() <= start.getTime()) { + throw new IllegalArgumentException("Given start date (" + start + + ") is after the existing end date (" + end + ')'); + } + } + + return this; + } + + public Builder limitResultsBefore(Date end) { + if (end == null) { + return this; + } + + FormField formField = new FormField(FORM_FIELD_END); + formField.addValue(end); + formFields.put(formField.getVariable(), formField); + + FormField startFormField = formFields.get(FORM_FIELD_START); + if (startFormField != null) { + Date start; + try { + start = startFormField.getFirstValueAsDate(); + } catch (ParseException e) { + throw new IllegalStateException(e); + } + if (end.getTime() <= start.getTime()) { + throw new IllegalArgumentException("Given end date (" + end + + ") is before the existing start date (" + start + ')'); + } + } + + return this; + } + + public Builder setResultPageSize(Integer max) { + if (max == null) { + maxResults = -1; + return this; + } + return setResultPageSizeTo(max.intValue()); + } + + public Builder setResultPageSizeTo(int max) { + if (max < 0) { + throw new IllegalArgumentException(); + } + this.maxResults = max; + return this; + } + + /** + * Only return the count of messages the query yields, not the actual messages. Note that not all services + * return a correct count, some return an approximate count. + * + * @return an reference to this builder. + * @see XEP-0059 § 2.7 + */ + public Builder onlyReturnMessageCount() { + return setResultPageSizeTo(0); + } + + public Builder withAdditionalFormField(FormField formField) { + formFields.put(formField.getVariable(), formField); + return this; + } + + public Builder withAdditionalFormFields(List additionalFields) { + for (FormField formField : additionalFields) { + withAdditionalFormField(formField); + } + return this; + } + + public Builder afterUid(String afterUid) { + this.afterUid = StringUtils.requireNullOrNotEmpty(afterUid, "afterUid must not be empty"); + return this; + } + + /** + * Specifies a message UID as 'before' anchor for the query. Note that unlike {@link #afterUid(String)} this + * method also accepts the empty String to query the last page of an archive (c.f. XEP-0059 § 2.5). + * + * @param beforeUid a message UID acting as 'before' query anchor. + * @return an instance to this builder. + */ + public Builder beforeUid(String beforeUid) { + // We don't perform any argument validation, since every possible argument (null, empty string, + // non-empty string) is valid. + this.beforeUid = beforeUid; + return this; + } + + public Builder queryLastPage() { + return beforeUid(""); + } + + public MamQueryArgs build() { + return new MamQueryArgs(this); + } + } + } + /** * Query archive with a maximum amount of results. * @@ -154,7 +455,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult queryArchive(Integer max) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { return queryArchive(null, max, null, null, null, null); @@ -170,7 +474,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult queryArchive(Jid withJid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { return queryArchive(null, null, null, null, withJid, null); @@ -190,7 +497,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult queryArchive(Date start, Date end) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { return queryArchive(null, null, start, end, null, null); @@ -206,7 +516,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult queryArchive(List additionalFields) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { return queryArchive(null, null, null, null, null, additionalFields); @@ -223,7 +536,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult queryArchiveWithStartDate(Date start) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { return queryArchive(null, null, start, null, null, null); @@ -240,7 +556,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult queryArchiveWithEndDate(Date end) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { return queryArchive(null, null, null, end, null, null); @@ -262,7 +581,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult queryArchive(Integer max, Date start, Date end, Jid withJid, List additionalFields) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { @@ -287,72 +609,53 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult queryArchive(String node, Integer max, Date start, Date end, Jid withJid, List additionalFields) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { - DataForm dataForm = null; - String queryId = UUID.randomUUID().toString(); + MamQueryArgs mamQueryArgs = MamQueryArgs.builder() + .queryNode(node) + .setResultPageSize(max) + .limitResultsSince(start) + .limitResultsBefore(end) + .limitResultsToJid(withJid) + .withAdditionalFormFields(additionalFields) + .build(); - if (start != null || end != null || withJid != null || additionalFields != null) { - dataForm = getNewMamForm(); - addStart(start, dataForm); - addEnd(end, dataForm); - addWithJid(withJid, dataForm); - addAdditionalFields(additionalFields, dataForm); - } + MamQuery mamQuery = queryArchive(mamQueryArgs); + return new MamQueryResult(mamQuery); + } + + public MamQuery queryArchive(MamQueryArgs mamQueryArgs) throws NoResponseException, XMPPErrorException, + NotConnectedException, NotLoggedInException, InterruptedException { + String queryId = UUID.randomUUID().toString(); + String node = mamQueryArgs.node; + DataForm dataForm = mamQueryArgs.getDataForm(); MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, node, dataForm); mamQueryIQ.setType(IQ.Type.set); mamQueryIQ.setTo(archiveAddress); - addResultsLimit(max, mamQueryIQ); + mamQueryArgs.maybeAddRsmSet(mamQueryIQ); + return queryArchive(mamQueryIQ); } - private static void addAdditionalFields(List additionalFields, DataForm dataForm) { - if (additionalFields == null) { - return; - } - for (FormField formField : additionalFields) { - dataForm.addField(formField); - } - } - - private static void addResultsLimit(Integer max, MamQueryIQ mamQueryIQ) { - if (max == null) { - return; - } - RSMSet rsmSet = new RSMSet(max); - mamQueryIQ.addExtension(rsmSet); - + private static FormField getWithFormField(Jid withJid) { + FormField formField = new FormField(FORM_FIELD_WITH); + formField.addValue(withJid.toString()); + return formField; } private static void addWithJid(Jid withJid, DataForm dataForm) { if (withJid == null) { return; } - FormField formField = new FormField("with"); - formField.addValue(withJid.toString()); - dataForm.addField(formField); - } - - private static void addEnd(Date end, DataForm dataForm) { - if (end == null) { - return; - } - FormField formField = new FormField("end"); - formField.addValue(XmppDateTime.formatXEP0082Date(end)); - dataForm.addField(formField); - } - - private static void addStart(Date start, DataForm dataForm) { - if (start == null) { - return; - } - FormField formField = new FormField("start"); - formField.addValue(XmppDateTime.formatXEP0082Date(start)); + FormField formField = getWithFormField(withJid); dataForm.addField(formField); } @@ -367,16 +670,18 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult page(DataForm dataForm, RSMSet rsmSet) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { - return page(null, dataForm, rsmSet); - } /** - * Returns a page of the archive. + * Returns a page of the archive. This is a low-level method, you possibly do not want to use it directly unless you + * know what you are doing. * * @param node The PubSub node name, can be null * @param dataForm @@ -387,7 +692,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult page(String node, DataForm dataForm, RSMSet rsmSet) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { @@ -395,7 +703,8 @@ public final class MamManager extends Manager { mamQueryIQ.setType(IQ.Type.set); mamQueryIQ.setTo(archiveAddress); mamQueryIQ.addExtension(rsmSet); - return queryArchive(mamQueryIQ); + MamQuery mamQuery = queryArchive(mamQueryIQ); + return new MamQueryResult(mamQuery); } /** @@ -411,7 +720,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link MamQuery#pageNext(int)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult pageNext(MamQueryResult mamQueryResult, int count) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { RSMSet previousResultRsmSet = mamQueryResult.mamFin.getRSMSet(); @@ -432,7 +744,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link MamQuery#pagePrevious(int)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult pagePrevious(MamQueryResult mamQueryResult, int count) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { RSMSet previousResultRsmSet = mamQueryResult.mamFin.getRSMSet(); @@ -462,7 +777,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NoResponseException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult pageBefore(Jid chatJid, String messageUid, int max) throws XMPPErrorException, NotLoggedInException, NotConnectedException, InterruptedException, NoResponseException { RSMSet rsmSet = new RSMSet(null, messageUid, -1, -1, null, max, null, -1); @@ -486,7 +804,10 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NoResponseException + * @deprecated use {@link #queryArchive(MamQueryArgs)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult pageAfter(Jid chatJid, String messageUid, int max) throws XMPPErrorException, NotLoggedInException, NotConnectedException, InterruptedException, NoResponseException { RSMSet rsmSet = new RSMSet(messageUid, null, -1, -1, null, max, null, -1); @@ -506,12 +827,26 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NoResponseException + * @deprecated use {@link #queryMostRecentPage(Jid, int)} instead. */ + @Deprecated + // TODO Remove in Smack 4.4 public MamQueryResult mostRecentPage(Jid chatJid, int max) throws XMPPErrorException, NotLoggedInException, NotConnectedException, InterruptedException, NoResponseException { return pageBefore(chatJid, "", max); } + public MamQuery queryMostRecentPage(Jid jid, int max) throws NoResponseException, XMPPErrorException, + NotConnectedException, NotLoggedInException, InterruptedException { + MamQueryArgs mamQueryArgs = MamQueryArgs.builder() + // Produces an empty element for XEP-0059 § 2.5 + .queryLastPage() + .limitResultsToJid(jid) + .setResultPageSize(max) + .build(); + return queryArchive(mamQueryArgs); + } + /** * Get the form fields supported by the server. * @@ -550,8 +885,14 @@ public final class MamManager extends Manager { return mamResponseQueryIq.getDataForm().getFields(); } - private MamQueryResult queryArchive(MamQueryIQ mamQueryIq) throws NoResponseException, XMPPErrorException, - NotConnectedException, InterruptedException, NotLoggedInException { + private MamQuery queryArchive(MamQueryIQ mamQueryIq) throws NoResponseException, XMPPErrorException, + NotConnectedException, InterruptedException, NotLoggedInException { + MamQueryPage mamQueryPage = queryArchivePage(mamQueryIq); + return new MamQuery(mamQueryPage, mamQueryIq.getNode(), DataForm.from(mamQueryIq)); + } + + private MamQueryPage queryArchivePage(MamQueryIQ mamQueryIq) throws NoResponseException, XMPPErrorException, + NotConnectedException, InterruptedException, NotLoggedInException { final XMPPConnection connection = getAuthenticatedConnectionOrThrow(); MamFinIQ mamFinIQ; @@ -569,27 +910,24 @@ public final class MamManager extends Manager { resultCollector.cancel(); } - List forwardedMessages = new ArrayList<>(resultCollector.getCollectedCount()); - - for (Message resultMessage = resultCollector - .pollResult(); resultMessage != null; resultMessage = resultCollector.pollResult()) { - MamElements.MamResultExtension mamResultExtension = MamElements.MamResultExtension.from(resultMessage); - forwardedMessages.add(mamResultExtension.getForwarded()); - } - - return new MamQueryResult(forwardedMessages, mamFinIQ, mamQueryIq.getNode(), DataForm.from(mamQueryIq)); + return new MamQueryPage(resultCollector, mamFinIQ); } /** * MAM query result class. * */ + @Deprecated public static final class MamQueryResult { public final List forwardedMessages; public final MamFinIQ mamFin; private final String node; private final DataForm form; + private MamQueryResult(MamQuery mamQuery) { + this(mamQuery.mamQueryPage.forwardedMessages, mamQuery.mamQueryPage.mamFin, mamQuery.node, mamQuery.form); + } + private MamQueryResult(List forwardedMessages, MamFinIQ mamFin, String node, DataForm form) { this.forwardedMessages = forwardedMessages; this.mamFin = mamFin; @@ -598,6 +936,124 @@ public final class MamManager extends Manager { } } + public final class MamQuery { + private final String node; + private final DataForm form; + + private MamQueryPage mamQueryPage; + + private MamQuery(MamQueryPage mamQueryPage, String node, DataForm form) { + this.node = node; + this.form = form; + + this.mamQueryPage = mamQueryPage; + } + + public boolean isComplete() { + return mamQueryPage.getMamFinIq().isComplete(); + } + + public List getMessages() { + return mamQueryPage.messages; + } + + public List getMamResultExtensions() { + return mamQueryPage.mamResultExtensions; + } + + private List page(RSMSet requestRsmSet) throws NoResponseException, XMPPErrorException, + NotConnectedException, NotLoggedInException, InterruptedException { + MamQueryIQ mamQueryIQ = new MamQueryIQ(UUID.randomUUID().toString(), node, form); + mamQueryIQ.setType(IQ.Type.set); + mamQueryIQ.setTo(archiveAddress); + mamQueryIQ.addExtension(requestRsmSet); + + mamQueryPage = queryArchivePage(mamQueryIQ); + + return mamQueryPage.messages; + } + + private RSMSet getPreviousRsmSet() { + return mamQueryPage.getMamFinIq().getRSMSet(); + } + + public List pageNext(int count) throws NoResponseException, XMPPErrorException, NotConnectedException, + NotLoggedInException, InterruptedException { + RSMSet previousResultRsmSet = getPreviousRsmSet(); + RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getLast(), RSMSet.PageDirection.after); + return page(requestRsmSet); + } + + public List pagePrevious(int count) throws NoResponseException, XMPPErrorException, + NotConnectedException, NotLoggedInException, InterruptedException { + RSMSet previousResultRsmSet = getPreviousRsmSet(); + RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getLast(), RSMSet.PageDirection.before); + return page(requestRsmSet); + } + + public int getMessageCount() { + return getMessages().size(); + } + + public MamQueryPage getPage() { + return mamQueryPage; + } + } + + public static final class MamQueryPage { + private final MamFinIQ mamFin; + private final List mamResultCarrierMessages; + private final List mamResultExtensions; + private final List forwardedMessages; + private final List messages; + + private MamQueryPage(StanzaCollector stanzaCollector, MamFinIQ mamFin) { + this.mamFin = mamFin; + + List mamResultCarrierStanzas = stanzaCollector.getCollectedStanzasAfterCancelled(); + + List mamResultCarrierMessages = new ArrayList<>(mamResultCarrierStanzas.size()); + List mamResultExtensions = new ArrayList<>(mamResultCarrierStanzas.size()); + List forwardedMessages = new ArrayList<>(mamResultCarrierStanzas.size()); + + for (Stanza mamResultStanza : mamResultCarrierStanzas) { + Message resultMessage = (Message) mamResultStanza; + + mamResultCarrierMessages.add(resultMessage); + + MamElements.MamResultExtension mamResultExtension = MamElements.MamResultExtension.from(resultMessage); + mamResultExtensions.add(mamResultExtension); + + forwardedMessages.add(mamResultExtension.getForwarded()); + } + + this.mamResultCarrierMessages = Collections.unmodifiableList(mamResultCarrierMessages); + this.mamResultExtensions = Collections.unmodifiableList(mamResultExtensions); + this.forwardedMessages = Collections.unmodifiableList(forwardedMessages); + this.messages = Collections.unmodifiableList(Forwarded.extractMessagesFrom(forwardedMessages)); + } + + public List getMessages() { + return messages; + } + + public List getForwarded() { + return forwardedMessages; + } + + public List getMamResultExtensions() { + return mamResultExtensions; + } + + public List getMamResultCarrierMessages() { + return mamResultCarrierMessages; + } + + public MamFinIQ getMamFinIq() { + return mamFin; + } + } + private void ensureMamQueryResultMatchesThisManager(MamQueryResult mamQueryResult) { EntityFullJid localAddress = connection().getUser(); EntityBareJid localBareAddress = null; @@ -649,6 +1105,32 @@ public final class MamManager extends Manager { return form; } + /** + * Lookup the archive's message ID of the latest message in the archive. Returns {@code null} if the archive is + * empty. + * + * @return the ID of the lastest message or {@code null}. + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws NotLoggedInException + * @throws InterruptedException + * @since 4.3.0 + */ + public String getMessageUidOfLatestMessage() throws NoResponseException, XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException { + MamQueryArgs mamQueryArgs = MamQueryArgs.builder() + .setResultPageSize(1) + .queryLastPage() + .build(); + + MamQuery mamQuery = queryArchive(mamQueryArgs); + if (mamQuery.getMessages().isEmpty()) { + return null; + } + + return mamQuery.getMamResultExtensions().get(0).getId(); + } + /** * Get the preferences stored in the server. * @@ -682,7 +1164,9 @@ public final class MamManager extends Manager { * @throws NotConnectedException * @throws InterruptedException * @throws NotLoggedInException + * @deprecated use {@link #updateArchivingPreferences(MamPrefs)} instead. */ + @Deprecated public MamPrefsResult updateArchivingPreferences(List alwaysJids, List neverJids, DefaultBehavior defaultBehavior) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { @@ -691,6 +1175,46 @@ public final class MamManager extends Manager { return queryMamPrefs(mamPrefIQ); } + /** + * Update the preferences in the server. + * + * @param mamPrefs + * @return the currently active preferences after the operation. + * @throws NoResponseException + * @throws XMPPErrorException + * @throws NotConnectedException + * @throws InterruptedException + * @throws NotLoggedInException + * @since 4.3.0 + */ + public MamPrefsResult updateArchivingPreferences(MamPrefs mamPrefs) throws NoResponseException, XMPPErrorException, + NotConnectedException, InterruptedException, NotLoggedInException { + MamPrefsIQ mamPrefIQ = mamPrefs.constructMamPrefsIq(); + return queryMamPrefs(mamPrefIQ); + } + + public MamPrefsResult enableMamForAllMessages() throws NoResponseException, XMPPErrorException, + NotConnectedException, NotLoggedInException, InterruptedException { + return setDefaultBehavior(DefaultBehavior.always); + } + + public MamPrefsResult enableMamForRosterMessages() throws NoResponseException, XMPPErrorException, + NotConnectedException, NotLoggedInException, InterruptedException { + return setDefaultBehavior(DefaultBehavior.roster); + } + + public MamPrefsResult setDefaultBehavior(DefaultBehavior desiredDefaultBehavior) throws NoResponseException, + XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException { + MamPrefsResult mamPrefsResult = retrieveArchivingPreferences(); + if (mamPrefsResult.mamPrefs.getDefault() == desiredDefaultBehavior) { + return mamPrefsResult; + } + + MamPrefs mamPrefs = mamPrefsResult.asMamPrefs(); + mamPrefs.setDefaultBehavior(desiredDefaultBehavior); + return updateArchivingPreferences(mamPrefs); + } + /** * MAM preferences result class. * @@ -703,6 +1227,43 @@ public final class MamManager extends Manager { this.mamPrefs = mamPrefs; this.form = form; } + + public MamPrefs asMamPrefs() { + return new MamPrefs(this); + } + } + + public static final class MamPrefs { + private final List alwaysJids; + private final List neverJids; + private DefaultBehavior defaultBehavior; + + private MamPrefs(MamPrefsResult mamPrefsResult) { + MamPrefsIQ mamPrefsIq = mamPrefsResult.mamPrefs; + this.alwaysJids = new ArrayList<>(mamPrefsIq.getAlwaysJids()); + this.neverJids = new ArrayList<>(mamPrefsIq.getNeverJids()); + this.defaultBehavior = mamPrefsIq.getDefault(); + } + + public void setDefaultBehavior(DefaultBehavior defaultBehavior) { + this.defaultBehavior = Objects.requireNonNull(defaultBehavior, "defaultBehavior must not be null"); + } + + public DefaultBehavior getDefaultBehavior() { + return defaultBehavior; + } + + public List getAlwaysJids() { + return alwaysJids; + } + + public List getNeverJids() { + return neverJids; + } + + private MamPrefsIQ constructMamPrefsIq() { + return new MamPrefsIQ(alwaysJids, neverJids, defaultBehavior); + } } private MamPrefsResult queryMamPrefs(MamPrefsIQ mamPrefsIQ) throws NoResponseException, XMPPErrorException, diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java index c25790544..d6655ae4f 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016 Fernando Ramirez + * Copyright 2016 Fernando Ramirez, 2018 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,24 @@ */ package org.jivesoftware.smackx.mam; -import java.lang.reflect.Method; +import static org.junit.Assert.assertEquals; + import java.util.ArrayList; import java.util.Date; import java.util.List; +import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs; import org.jivesoftware.smackx.mam.element.MamElements; import org.jivesoftware.smackx.xdata.packet.DataForm; -import org.junit.Assert; import org.junit.Test; import org.jxmpp.jid.Jid; -import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.jid.JidTestUtil; import org.jxmpp.util.XmppDateTime; public class FiltersTest extends MamTest { - private static String getMamXMemberWith(List fieldsNames, List fieldsValues) { + private static String getMamXMemberWith(List fieldsNames, List fieldsValues) { String xml = "" + "" + "" + MamElements.NAMESPACE + "" + ""; @@ -47,115 +48,47 @@ public class FiltersTest extends MamTest { @Test public void checkStartDateFilter() throws Exception { - Method methodAddStartDateFilter = MamManager.class.getDeclaredMethod("addStart", Date.class, DataForm.class); - methodAddStartDateFilter.setAccessible(true); - Date date = new Date(); - DataForm dataForm = getNewMamForm(); - methodAddStartDateFilter.invoke(mamManager, date, dataForm); + + MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsSince(date).build(); + DataForm dataForm = mamQueryArgs.getDataForm(); List fields = new ArrayList<>(); fields.add("start"); List values = new ArrayList<>(); values.add(XmppDateTime.formatXEP0082Date(date)); - Assert.assertEquals(dataForm.toXML(null).toString(), getMamXMemberWith(fields, values)); + assertEquals(getMamXMemberWith(fields, values), dataForm.toXML(null).toString()); } @Test public void checkEndDateFilter() throws Exception { - Method methodAddEndDateFilter = MamManager.class.getDeclaredMethod("addEnd", Date.class, DataForm.class); - methodAddEndDateFilter.setAccessible(true); - Date date = new Date(); - DataForm dataForm = getNewMamForm(); - methodAddEndDateFilter.invoke(mamManager, date, dataForm); + + MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsBefore(date).build(); + DataForm dataForm = mamQueryArgs.getDataForm(); List fields = new ArrayList<>(); fields.add("end"); List values = new ArrayList<>(); values.add(XmppDateTime.formatXEP0082Date(date)); - Assert.assertEquals(dataForm.toXML(null).toString(), getMamXMemberWith(fields, values)); + assertEquals(getMamXMemberWith(fields, values), dataForm.toXML(null).toString()); } @Test public void checkWithJidFilter() throws Exception { - Method methodAddJidFilter = MamManager.class.getDeclaredMethod("addWithJid", Jid.class, DataForm.class); - methodAddJidFilter.setAccessible(true); + Jid jid = JidTestUtil.BARE_JID_1; - String jid = "test@jid.com"; - DataForm dataForm = getNewMamForm(); - methodAddJidFilter.invoke(mamManager, JidCreate.from(jid), dataForm); + MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsToJid(jid).build(); + DataForm dataForm = mamQueryArgs.getDataForm(); List fields = new ArrayList<>(); fields.add("with"); - List values = new ArrayList<>(); + List values = new ArrayList<>(); values.add(jid); - Assert.assertEquals(dataForm.toXML(null).toString(), getMamXMemberWith(fields, values)); - } - - @Test - public void checkMultipleFilters() throws Exception { - Method methodAddStartDateFilter = MamManager.class.getDeclaredMethod("addStart", Date.class, DataForm.class); - methodAddStartDateFilter.setAccessible(true); - Method methodAddEndDateFilter = MamManager.class.getDeclaredMethod("addEnd", Date.class, DataForm.class); - methodAddEndDateFilter.setAccessible(true); - Method methodAddJidFilter = MamManager.class.getDeclaredMethod("addWithJid", Jid.class, DataForm.class); - methodAddJidFilter.setAccessible(true); - - DataForm dataForm = getNewMamForm(); - Date date = new Date(); - String dateString = XmppDateTime.formatXEP0082Date(date); - String jid = "test@jid.com"; - - methodAddStartDateFilter.invoke(mamManager, date, dataForm); - methodAddEndDateFilter.invoke(mamManager, date, dataForm); - methodAddJidFilter.invoke(mamManager, JidCreate.from(jid), dataForm); - String dataFormResult = dataForm.toXML(null).toString(); - - List fields = new ArrayList<>(); - List values = new ArrayList<>(); - - fields.add("start"); - values.add(dateString); - Assert.assertNotEquals(dataFormResult, getMamXMemberWith(fields, values)); - - fields.add("end"); - values.add(dateString); - Assert.assertNotEquals(dataFormResult, getMamXMemberWith(fields, values)); - - fields.clear(); - values.clear(); - - fields.add("start"); - values.add(dateString); - fields.add("with"); - values.add(jid); - Assert.assertNotEquals(dataFormResult, getMamXMemberWith(fields, values)); - - fields.clear(); - values.clear(); - - fields.add("end"); - values.add(dateString); - fields.add("with"); - values.add(jid); - fields.add("start"); - values.add(dateString); - Assert.assertNotEquals(dataFormResult, getMamXMemberWith(fields, values)); - - fields.clear(); - values.clear(); - - fields.add("start"); - values.add(dateString); - fields.add("end"); - values.add(dateString); - fields.add("with"); - values.add(jid); - Assert.assertEquals(dataFormResult, getMamXMemberWith(fields, values)); + assertEquals(getMamXMemberWith(fields, values), dataForm.toXML(null).toString()); } } diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java index 48b014201..162d06564 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016 Fernando Ramirez + * Copyright 2016 Fernando Ramirez, 2018 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,10 @@ */ package org.jivesoftware.smackx.mam; -import java.lang.reflect.Method; - import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.StreamOpen; +import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs; import org.jivesoftware.smackx.mam.element.MamElements; import org.jivesoftware.smackx.mam.element.MamQueryIQ; import org.jivesoftware.smackx.xdata.packet.DataForm; @@ -37,16 +36,14 @@ public class ResultsLimitTest extends MamTest { @Test public void checkResultsLimit() throws Exception { - Method methodAddResultsLimit = MamManager.class.getDeclaredMethod("addResultsLimit", Integer.class, - MamQueryIQ.class); - methodAddResultsLimit.setAccessible(true); DataForm dataForm = getNewMamForm(); MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm); mamQueryIQ.setType(IQ.Type.set); mamQueryIQ.setStanzaId("sarasa"); - methodAddResultsLimit.invoke(mamManager, 10, mamQueryIQ); + MamQueryArgs mamQueryArgs = MamQueryArgs.builder().setResultPageSize(10).build(); + mamQueryArgs.maybeAddRsmSet(mamQueryIQ); Assert.assertEquals(mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), resultsLimitStanza); } diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java index 86ecb8ead..8e0557b85 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016 Fernando Ramirez + * Copyright 2016 Fernando Ramirez, 2018 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,17 @@ */ package org.jivesoftware.smackx.mam; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; +import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.junit.Assert.assertEquals; import org.jivesoftware.smack.packet.StreamOpen; +import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs; import org.jivesoftware.smackx.mam.element.MamElements; import org.jivesoftware.smackx.mam.element.MamQueryIQ; import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.packet.DataForm; -import org.junit.Assert; import org.junit.Test; public class RetrieveFormFieldsTest extends MamTest { @@ -46,19 +45,11 @@ public class RetrieveFormFieldsTest extends MamTest { MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId); mamQueryIQ.setStanzaId("sarasa"); - Assert.assertEquals(mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), retrieveFormFieldStanza); + assertEquals(retrieveFormFieldStanza, mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); } @Test public void checkAddAdditionalFieldsStanza() throws Exception { - Method methodAddAdditionalFields = MamManager.class.getDeclaredMethod("addAdditionalFields", List.class, - DataForm.class); - methodAddAdditionalFields.setAccessible(true); - - DataForm dataForm = getNewMamForm(); - - List additionalFields = new ArrayList<>(); - FormField field1 = new FormField("urn:example:xmpp:free-text-search"); field1.setType(FormField.Type.text_single); field1.addValue("Hi"); @@ -67,14 +58,15 @@ public class RetrieveFormFieldsTest extends MamTest { field2.setType(FormField.Type.jid_single); field2.addValue("Hi2"); - additionalFields.add(field1); - additionalFields.add(field2); - - methodAddAdditionalFields.invoke(mamManager, additionalFields, dataForm); + MamQueryArgs mamQueryArgs = MamQueryArgs.builder() + .withAdditionalFormField(field1) + .withAdditionalFormField(field2) + .build(); + DataForm dataForm = mamQueryArgs.getDataForm(); String dataFormResult = dataForm.toXML(null).toString(); - Assert.assertEquals(dataFormResult, additionalFieldsStanza); + assertXmlSimilar(additionalFieldsStanza, dataFormResult); } } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java index 940381398..99168f78a 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mam/MamIntegrationTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016 Fernando Ramirez + * Copyright 2016 Fernando Ramirez, 2018 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,23 +17,30 @@ package org.jivesoftware.smackx.mam; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeoutException; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotLoggedInException; +import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.filter.MessageWithBodiesFilter; import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Stanza; -import org.jivesoftware.smackx.forward.packet.Forwarded; -import org.jivesoftware.smackx.mam.MamManager.MamQueryResult; -import org.jivesoftware.smackx.mam.element.MamPrefsIQ; +import org.jivesoftware.smackx.mam.MamManager.MamQuery; +import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs; import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.TestNotPossibleException; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; import org.jxmpp.jid.EntityBareJid; public class MamIntegrationTest extends AbstractSmackIntegrationTest { @@ -41,7 +48,7 @@ public class MamIntegrationTest extends AbstractSmackIntegrationTest { private final MamManager mamManagerConTwo; public MamIntegrationTest(SmackIntegrationTestEnvironment environment) throws NoResponseException, - XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException { + XMPPErrorException, NotConnectedException, InterruptedException, TestNotPossibleException, NotLoggedInException { super(environment); mamManagerConTwo = MamManager.getInstanceFor(conTwo); @@ -50,33 +57,51 @@ public class MamIntegrationTest extends AbstractSmackIntegrationTest { throw new TestNotPossibleException("Message Archive Management (XEP-0313) is not supported by the server."); } + // Make sure MAM is archiving messages. + mamManagerConTwo.enableMamForAllMessages(); } @SmackIntegrationTest - public void mamTest() throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException, - NotLoggedInException { + public void mamTest() throws TimeoutException, Exception { EntityBareJid userOne = conOne.getUser().asEntityBareJid(); EntityBareJid userTwo = conTwo.getUser().asEntityBareJid(); - // Make sure MAM is archiving messages. - mamManagerConTwo.updateArchivingPreferences(null, null, MamPrefsIQ.DefaultBehavior.always); - Message message = new Message(userTwo); String messageId = message.setStanzaId(); - String messageBody = "test message"; + final String messageBody = "Test MAM message (" + testRunId + ')'; message.setBody(messageBody); - conOne.sendStanza(message); + final SimpleResultSyncPoint messageReceived = new SimpleResultSyncPoint(); - int pageSize = 20; - MamQueryResult mamQueryResult = mamManagerConTwo.queryArchive(pageSize, null, null, userOne, null); + final StanzaListener stanzaListener = new StanzaListener() { + @Override + public void processStanza(Stanza stanza) { + Message message = (Message) stanza; + if (message.getBody().equals(messageBody)) { + messageReceived.signal(); + } + } + }; + conTwo.addAsyncStanzaListener(stanzaListener, MessageWithBodiesFilter.INSTANCE); - while (!mamQueryResult.mamFin.isComplete()) { - mamQueryResult = mamManagerConTwo.pageNext(mamQueryResult, pageSize); + try { + conOne.sendStanza(message); + + messageReceived.waitForResult(timeout); + } finally { + conTwo.removeAsyncStanzaListener(stanzaListener); } - List forwardedMessages = mamQueryResult.forwardedMessages; - Message mamMessage = (Message) forwardedMessages.get(forwardedMessages.size() - 1).getForwardedStanza(); + MamQueryArgs mamQueryArgs = MamQueryArgs.builder() + .setResultPageSizeTo(1) + .limitResultsToJid(userOne) + .queryLastPage() + .build(); + MamQuery mamQuery = mamManagerConTwo.queryArchive(mamQueryArgs); + + assertEquals(1, mamQuery.getMessages().size()); + + Message mamMessage = mamQuery.getMessages().get(0); assertEquals(messageId, mamMessage.getStanzaId()); assertEquals(messageBody, mamMessage.getBody()); @@ -84,4 +109,87 @@ public class MamIntegrationTest extends AbstractSmackIntegrationTest { assertEquals(userTwo, mamMessage.getTo()); } + @SmackIntegrationTest + public void mamPageTest() throws TimeoutException, Exception { + final int messagesPerPage = 10; + final int numPages = 3; + final int totalMessages = messagesPerPage * numPages; + final List outgoingMessages = new ArrayList<>(totalMessages); + final EntityBareJid userOne = conOne.getUser().asEntityBareJid(); + final EntityBareJid userTwo = conTwo.getUser().asEntityBareJid(); + final SimpleResultSyncPoint allMessagesReceived = new SimpleResultSyncPoint(); + final String lastMessageArchiveUid = mamManagerConTwo.getMessageUidOfLatestMessage(); + + for (int i = 0; i < totalMessages; i++) { + String messageBody = "MAM Page Test " + testRunId + ' ' + (i + 1); + Message message = new Message(userTwo, messageBody); + outgoingMessages.add(message); + } + + final String lastBody = outgoingMessages.get(outgoingMessages.size() - 1).getBody(); + + final StanzaListener stanzaListener = new StanzaListener() { + @Override + public void processStanza(Stanza stanza) { + Message message = (Message) stanza; + if (message.getBody().equals(lastBody)) { + allMessagesReceived.signal(); + } + } + }; + conTwo.addAsyncStanzaListener(stanzaListener, MessageWithBodiesFilter.INSTANCE); + + try { + for (Message message : outgoingMessages) { + conOne.sendStanza(message); + } + + allMessagesReceived.waitForResult(timeout); + } finally { + conTwo.removeAsyncStanzaListener(stanzaListener); + } + + MamQueryArgs mamQueryArgs = MamQueryArgs.builder() + .setResultPageSize(messagesPerPage) + .limitResultsToJid(userOne) + .afterUid(lastMessageArchiveUid) + .build(); + + MamQuery mamQuery = mamManagerConTwo.queryArchive(mamQueryArgs); + + assertFalse(mamQuery.isComplete()); + assertEquals(messagesPerPage, mamQuery.getMessageCount()); + + List> pages = new ArrayList<>(numPages); + pages.add(mamQuery.getMessages()); + + for (int additionalPageRequestNum = 0; additionalPageRequestNum < numPages - 1; additionalPageRequestNum++) { + List page = mamQuery.pageNext(messagesPerPage); + + boolean isLastQuery = additionalPageRequestNum == numPages - 2; + if (isLastQuery) { + assertTrue(mamQuery.isComplete()); + } else { + assertFalse(mamQuery.isComplete()); + } + + assertEquals(messagesPerPage, page.size()); + + pages.add(page); + } + + List queriedMessages = new ArrayList<>(totalMessages); + for (List messages : pages) { + queriedMessages.addAll(messages); + } + + assertEquals(outgoingMessages.size(), queriedMessages.size()); + + for (int i = 0; i < outgoingMessages.size(); i++) { + Message outgoingMessage = outgoingMessages.get(i); + Message queriedMessage = queriedMessages.get(i); + + assertEquals(outgoingMessage.getBody(), queriedMessage.getBody()); + } + } } diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java index 174063acd..d598136a9 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java @@ -325,16 +325,16 @@ public final class OmemoManager extends Manager { * Return a list of all OMEMO messages that were found in the MAM query result, that could be successfully decrypted. * Normal cleartext messages are also added to this list. * - * @param mamQueryResult mamQueryResult + * @param mamQuery The MAM query * @return list of decrypted OmemoMessages * @throws InterruptedException Exception * @throws XMPPException.XMPPErrorException Exception * @throws SmackException.NotConnectedException Exception * @throws SmackException.NoResponseException Exception */ - public List decryptMamQueryResult(MamManager.MamQueryResult mamQueryResult) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { + public List decryptMamQueryResult(MamManager.MamQuery mamQuery) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { List l = new ArrayList<>(); - l.addAll(getOmemoService().decryptMamQueryResult(this, mamQueryResult)); + l.addAll(getOmemoService().decryptMamQueryResult(this, mamQuery)); return l; } diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java index 1ad4ae3ea..d6781fff2 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoService.java @@ -54,7 +54,6 @@ import org.jivesoftware.smack.util.Async; import org.jivesoftware.smackx.carbons.CarbonCopyReceivedListener; import org.jivesoftware.smackx.carbons.CarbonManager; import org.jivesoftware.smackx.carbons.packet.CarbonExtension; -import org.jivesoftware.smackx.forward.packet.Forwarded; import org.jivesoftware.smackx.mam.MamManager; import org.jivesoftware.smackx.muc.MultiUserChat; import org.jivesoftware.smackx.muc.MultiUserChatManager; @@ -1127,21 +1126,21 @@ public abstract class OmemoService decryptMamQueryResult(OmemoManager omemoManager, MamManager.MamQueryResult mamQueryResult) + List decryptMamQueryResult(OmemoManager omemoManager, MamManager.MamQuery mamQuery) throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { List result = new ArrayList<>(); - for (Forwarded f : mamQueryResult.forwardedMessages) { - if (OmemoManager.stanzaContainsOmemoElement(f.getForwardedStanza())) { + for (Message message : mamQuery.getMessages()) { + if (OmemoManager.stanzaContainsOmemoElement(message)) { // Decrypt OMEMO messages try { - result.add(processLocalMessage(omemoManager, f.getForwardedStanza().getFrom().asBareJid(), (Message) f.getForwardedStanza())); + result.add(processLocalMessage(omemoManager, message.getFrom().asBareJid(), message)); } catch (NoRawSessionException | CorruptedOmemoKeyException | CryptoFailedException e) { LOGGER.log(Level.WARNING, "decryptMamQueryResult failed to decrypt message from " - + f.getForwardedStanza().getFrom() + " due to corrupted session/key: " + e.getMessage()); + + message.getFrom() + " due to corrupted session/key: " + e.getMessage()); } } else { // Wrap cleartext messages - Message m = (Message) f.getForwardedStanza(); + Message m = message; result.add(new ClearTextMessage(m.getBody(), m, new OmemoMessageInformation(null, null, OmemoMessageInformation.CARBON.NONE, false))); } From 03a267a9259b6ea7a4631c3cdb89593a7a6fdca8 Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 12 Jun 2018 09:49:08 +0200 Subject: [PATCH 22/23] fix: Prevent attempt to construct invalid address When no join was properly registered, a nickname will not be defined. In that case, attempting to construct the from address for the 'leave' presence stanza will result in: java.lang.IllegalArgumentException: The Resourcepart must not be null This commit prevents that, by verifying that the nickname is non-null, before sending that stanza. --- .../main/java/org/jivesoftware/smackx/muc/MultiUserChat.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index c22995cd6..56b25ac87 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -756,6 +756,10 @@ public class MultiUserChat { // throw. userHasLeft(); + if (nickname == null) { + return; + } + // We leave a room by sending a presence packet where the "to" // field is in the form "roomName@service/nickname" Presence leavePresence = new Presence(Presence.Type.unavailable); From 6ba9218c7779f4051c68b2c47d58306eb41f4c9e Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 12 Jun 2018 11:14:38 +0200 Subject: [PATCH 23/23] Introduce myRoomJid EntityFullJid in MultiUserChat --- .../smackx/muc/MultiUserChat.java | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index f2f2488b8..cdf1adc50 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -57,7 +57,7 @@ import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Stanza; -import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.packet.DiscoverInfo; @@ -149,7 +149,7 @@ public class MultiUserChat { private final StanzaListener declinesListener; private String subject; - private Resourcepart nickname; + private EntityFullJid myRoomJid; private boolean joined = false; private StanzaCollector messageCollector; @@ -208,7 +208,7 @@ public class MultiUserChat { LOGGER.warning("Presence not from a full JID: " + presence.getFrom()); return; } - final String myRoomJID = MultiUserChat.this.room + "/" + nickname; + final EntityFullJid myRoomJID = myRoomJid; final boolean isUserStatusModification = presence.getFrom().equals(myRoomJID); asyncButOrdered.performAsyncButOrdered(MultiUserChat.this, new Runnable() { @@ -383,7 +383,9 @@ public class MultiUserChat { // This presence must be send from a full JID. We use the resourcepart of this JID as nick, since the room may // performed roomnick rewriting - this.nickname = presence.getFrom().asEntityFullJidIfPossible().getResourcepart(); + Resourcepart receivedNickname = presence.getFrom().getResourceOrThrow(); + setNickname(receivedNickname); + joined = true; // Update the list of joined rooms @@ -391,6 +393,10 @@ public class MultiUserChat { return presence; } + private void setNickname(Resourcepart nickname) { + this.myRoomJid = JidCreate.entityFullFrom(room, nickname); + } + /** * Get a new MUC enter configuration builder. * @@ -486,6 +492,7 @@ public class MultiUserChat { * @deprecated use {@link #createOrJoin(MucEnterConfiguration)} instead. */ @Deprecated + // TODO Remove in Smack 4.4 public MucCreateConfigFormHandle createOrJoin(Resourcepart nickname, String password, @SuppressWarnings("deprecation") DiscussionHistory history, long timeout) throws NoResponseException, XMPPErrorException, InterruptedException, MucAlreadyJoinedException, NotConnectedException, NotAMucServiceException { MucEnterConfiguration.Builder builder = getEnterConfigurationBuilder(nickname).withPassword( @@ -684,6 +691,7 @@ public class MultiUserChat { * @deprecated use {@link #join(MucEnterConfiguration)} instead. */ @Deprecated + // TODO Remove in Smack 4.4 public void join( Resourcepart nickname, String password, @@ -759,7 +767,7 @@ public class MultiUserChat { // We leave a room by sending a presence packet where the "to" // field is in the form "roomName@service/nickname" Presence leavePresence = new Presence(Presence.Type.unavailable); - leavePresence.setTo(JidCreate.fullFrom(room, nickname)); + leavePresence.setTo(myRoomJid); connection.sendStanza(leavePresence); } @@ -1096,7 +1104,11 @@ public class MultiUserChat { * @return the nickname currently being used. */ public Resourcepart getNickname() { - return nickname; + final EntityFullJid myRoomJid = this.myRoomJid; + if (myRoomJid == null) { + return null; + } + return myRoomJid.getResourcepart(); } /** @@ -1114,7 +1126,7 @@ public class MultiUserChat { * @throws MucNotJoinedException */ public synchronized void changeNickname(Resourcepart nickname) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, MucNotJoinedException { - StringUtils.requireNotNullOrEmpty(nickname, "Nickname must not be null or blank."); + Objects.requireNonNull(nickname, "Nickname must not be null or blank."); // Check that we already have joined the room before attempting to change the // nickname. if (!joined) { @@ -1137,7 +1149,8 @@ public class MultiUserChat { // exception will be thrown response.nextResultOrThrow(); - this.nickname = nickname; + // TODO: Shouldn't this handle nickname rewriting by the MUC service? + setNickname(nickname); } /** @@ -1152,7 +1165,11 @@ public class MultiUserChat { * @throws MucNotJoinedException */ public void changeAvailabilityStatus(String status, Presence.Mode mode) throws NotConnectedException, InterruptedException, MucNotJoinedException { - StringUtils.requireNotNullOrEmpty(nickname, "Nickname must not be null or blank."); + final EntityFullJid myRoomJid = this.myRoomJid; + if (myRoomJid == null) { + throw new MucNotJoinedException(this); + } + // Check that we already have joined the room before attempting to change the // availability status. if (!joined) { @@ -1163,7 +1180,7 @@ public class MultiUserChat { Presence joinPresence = new Presence(Presence.Type.available); joinPresence.setStatus(status); joinPresence.setMode(mode); - joinPresence.setTo(JidCreate.fullFrom(room, nickname)); + joinPresence.setTo(myRoomJid); // Send join packet. connection.sendStanza(joinPresence); @@ -2398,7 +2415,7 @@ public class MultiUserChat { // Reset occupant information. occupantsMap.clear(); - nickname = null; + myRoomJid = null; userHasLeft(); } else { @@ -2418,7 +2435,7 @@ public class MultiUserChat { // Reset occupant information. occupantsMap.clear(); - nickname = null; + myRoomJid = null; userHasLeft(); } } @@ -2437,7 +2454,7 @@ public class MultiUserChat { // Reset occupant information. occupantsMap.clear(); - nickname = null; + myRoomJid = null; userHasLeft(); } }