> E parse(CharSequence xml, Class providerClass, XmlPullParserKind parserKind)
+ public static > E parse(CharSequence xml, Class providerClass, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
P provider = providerClassToProvider(providerClass);
return parse(xml, provider, parserKind);
}
- public static > E parse(InputStream inputStream, Class providerClass, XmlPullParserKind parserKind)
+ public static > E parse(InputStream inputStream, Class providerClass, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
P provider = providerClassToProvider(providerClass);
return parse(inputStream, provider, parserKind);
}
- public static > E parse(Reader reader, Class providerClass, XmlPullParserKind parserKind)
+ public static > E parse(Reader reader, Class providerClass, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
P provider = providerClassToProvider(providerClass);
return parse(reader, provider, parserKind);
}
- public static E parse(CharSequence xml, Provider provider, XmlPullParserKind parserKind)
+ public static E parse(CharSequence xml, AbstractProvider provider, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
String xmlString = xml.toString();
Reader reader = new StringReader(xmlString);
return parse(reader, provider, parserKind);
}
- public static E parse(InputStream inputStream, Provider provider, XmlPullParserKind parserKind)
+ public static E parse(InputStream inputStream, AbstractProvider provider, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
return parse(inputStreamReader, provider, parserKind);
}
- public static E parse(Reader reader, Provider provider, XmlPullParserKind parserKind)
+ @SuppressWarnings("unchecked")
+ public static E parse(Reader reader, AbstractProvider abstractProvider, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
XmlPullParser parser = getParserFor(reader, parserKind);
- E element = provider.parse(parser);
+
+ final E element;
+ if (abstractProvider instanceof Provider) {
+ Provider provider = (Provider) abstractProvider;
+ element = provider.parse(parser);
+ } else if (abstractProvider instanceof IqProvider) {
+ IqData iqData = PacketParserUtils.parseIqData(parser);
+ parser.next();
+ ParserUtils.forwardToStartElement(parser);
+ IqProvider> iqProvider = (IqProvider>) abstractProvider;
+ element = (E) iqProvider.parse(parser, iqData);
+ } else {
+ throw new AssertionError();
+ }
+
return element;
}
@@ -132,7 +151,7 @@ public class SmackTestUtil {
}
@SuppressWarnings("unchecked")
- private static > P providerClassToProvider(Class providerClass) {
+ private static > P providerClassToProvider(Class providerClass) {
P provider;
try {
diff --git a/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebugger.java b/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebugger.java
index c4a8c90dd..40d2e5263 100644
--- a/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebugger.java
+++ b/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebugger.java
@@ -34,7 +34,13 @@ import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -419,38 +425,11 @@ public class EnhancedDebugger extends SmackDebugger {
// Create a special Reader that wraps the main Reader and logs data to the GUI.
ObservableReader debugReader = new ObservableReader(reader);
readerListener = new ReaderListener() {
- @Override
- public void read(final String str) {
- SwingUtilities.invokeLater(new Runnable() {
- @Override
- public void run() {
- if (EnhancedDebuggerWindow.PERSISTED_DEBUGGER &&
- !EnhancedDebuggerWindow.getInstance().isVisible()) {
- // Do not add content if the parent is not visible
- return;
- }
+ private final PriorityBlockingQueue buffer = new PriorityBlockingQueue<>();
- int index = str.lastIndexOf(">");
- if (index != -1) {
- if (receivedText.getLineCount() >= EnhancedDebuggerWindow.MAX_TABLE_ROWS) {
- try {
- receivedText.replaceRange("", 0, receivedText.getLineEndOffset(0));
- }
- catch (BadLocationException e) {
- LOGGER.log(Level.SEVERE, "Error with line offset, MAX_TABLE_ROWS is set too low: " + EnhancedDebuggerWindow.MAX_TABLE_ROWS, e);
- }
- }
- receivedText.append(str.substring(0, index + 1));
- receivedText.append(NEWLINE);
- if (str.length() > index) {
- receivedText.append(str.substring(index + 1));
- }
- }
- else {
- receivedText.append(str);
- }
- }
- });
+ @Override
+ public void read(final String string) {
+ addBatched(string, buffer, receivedText);
}
};
debugReader.addReaderListener(readerListener);
@@ -458,34 +437,11 @@ public class EnhancedDebugger extends SmackDebugger {
// Create a special Writer that wraps the main Writer and logs data to the GUI.
ObservableWriter debugWriter = new ObservableWriter(writer);
writerListener = new WriterListener() {
+ private final PriorityBlockingQueue buffer = new PriorityBlockingQueue<>();
+
@Override
- public void write(final String str) {
- SwingUtilities.invokeLater(new Runnable() {
- @Override
- public void run() {
- if (EnhancedDebuggerWindow.PERSISTED_DEBUGGER &&
- !EnhancedDebuggerWindow.getInstance().isVisible()) {
- // Do not add content if the parent is not visible
- return;
- }
-
- if (sentText.getLineCount() >= EnhancedDebuggerWindow.MAX_TABLE_ROWS) {
- try {
- sentText.replaceRange("", 0, sentText.getLineEndOffset(0));
- }
- catch (BadLocationException e) {
- LOGGER.log(Level.SEVERE, "Error with line offset, MAX_TABLE_ROWS is set too low: " + EnhancedDebuggerWindow.MAX_TABLE_ROWS, e);
- }
- }
-
- sentText.append(str);
- if (str.endsWith(">")) {
- sentText.append(NEWLINE);
- }
- }
- });
-
-
+ public void write(final String string) {
+ addBatched(string, buffer, sentText);
}
};
debugWriter.addWriterListener(writerListener);
@@ -497,6 +453,50 @@ public class EnhancedDebugger extends SmackDebugger {
}
+ private static void addBatched(String string, PriorityBlockingQueue buffer, JTextArea jTextArea) {
+ buffer.add(string);
+
+ SwingUtilities.invokeLater(() -> {
+ List linesToAdd = new ArrayList<>();
+ String data;
+ Instant start = Instant.now();
+ try {
+ // To reduce overhead/increase performance, try to process up to a certain amount of lines at the
+ // same time, when they arrive in rapid succession.
+ while (linesToAdd.size() < 50
+ && Duration.between(start, Instant.now()).compareTo(Duration.ofMillis(100)) < 0
+ && (data = buffer.poll(10, TimeUnit.MILLISECONDS)) != null) {
+ linesToAdd.add(data);
+ }
+ } catch (InterruptedException e) {
+ LOGGER.log(Level.FINER, "Interrupted wait-for-poll in addBatched(). Process all data now.", e);
+ }
+
+ if (linesToAdd.isEmpty()) {
+ return;
+ }
+
+ if (EnhancedDebuggerWindow.PERSISTED_DEBUGGER && !EnhancedDebuggerWindow.getInstance().isVisible()) {
+ // Do not add content if the parent is not visible
+ return;
+ }
+
+ // Delete lines from the top, if lines to be added will exceed the maximum.
+ int linesToDelete = jTextArea.getLineCount() + linesToAdd.size() - EnhancedDebuggerWindow.MAX_TABLE_ROWS;
+ if (linesToDelete > 0) {
+ try {
+ jTextArea.replaceRange("", 0, jTextArea.getLineEndOffset(linesToDelete - 1));
+ } catch (BadLocationException e) {
+ LOGGER.log(Level.SEVERE, "Error with line offset, MAX_TABLE_ROWS is set too low: "
+ + EnhancedDebuggerWindow.MAX_TABLE_ROWS, e);
+ }
+ }
+
+ // Add the new content.
+ jTextArea.append(String.join(NEWLINE, linesToAdd));
+ });
+ }
+
private void addAdhocPacketPanel() {
// Create UI elements for sending ad-hoc messages.
final JTextArea adhocMessages = new JTextArea();
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/carbons/CarbonManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/carbons/CarbonManager.java
index c020df7cf..f2e055386 100644
--- a/smack-experimental/src/main/java/org/jivesoftware/smackx/carbons/CarbonManager.java
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/carbons/CarbonManager.java
@@ -154,8 +154,7 @@ public final class CarbonManager extends Manager {
// because we also reset in authenticated() if the stream got not resumed, but for maximum correctness,
// also reset here.
enabled_state = false;
- boolean removed = connection().removeSyncStanzaListener(carbonsListener);
- assert removed;
+ connection().removeSyncStanzaListener(carbonsListener);
}
@Override
public void authenticated(XMPPConnection connection, boolean resumed) {
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/AbstractHttpUploadException.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/AbstractHttpUploadException.java
new file mode 100644
index 000000000..1eb51f2ca
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/AbstractHttpUploadException.java
@@ -0,0 +1,101 @@
+/**
+ *
+ * Copyright 2022 Micha Kurvers
+ *
+ * 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.httpfileupload;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.jivesoftware.smackx.httpfileupload.element.Slot;
+/**
+ * An exception class to provide additional information in case of exceptions during file uploading.
+ *
+ */
+public abstract class AbstractHttpUploadException extends IOException {
+
+ private static final long serialVersionUID = 1L;
+ private final long fileSize;
+ private final Slot slot;
+
+ protected AbstractHttpUploadException(long fileSize, Slot slot, String message) {
+ this(fileSize, slot, message, null);
+ }
+
+ protected AbstractHttpUploadException(long fileSize, Slot slot, String message, Throwable wrappedThrowable) {
+ super(message, wrappedThrowable);
+ this.fileSize = fileSize;
+ this.slot = slot;
+ }
+
+ public long getFileSize() {
+ return fileSize;
+ }
+
+ public URL getPutUrl() {
+ return slot.getPutUrl();
+ }
+
+ public Slot getSlot() {
+ return slot;
+ }
+
+ /**
+ * Exception thrown when http response returned after upload is not 200.
+ */
+ public static class HttpUploadErrorException extends AbstractHttpUploadException {
+
+ private static final long serialVersionUID = 8494356028399474995L;
+ private final int httpStatus;
+ private final String responseMsg;
+
+ public HttpUploadErrorException(int httpStatus, String responseMsg, long fileSize, Slot slot) {
+ super(fileSize, slot, "Error response " + httpStatus + " from server during file upload: "
+ + responseMsg + ", file size: " + fileSize + ", put URL: "
+ + slot.getPutUrl());
+ this.httpStatus = httpStatus;
+ this.responseMsg = responseMsg;
+ }
+
+ public int getHttpStatus() {
+ return httpStatus;
+ }
+
+ public String getResponseMsg() {
+ return responseMsg;
+ }
+
+ }
+
+ /**
+ * Exception thrown when an unexpected exception occurred during the upload.
+ */
+ public static class HttpUploadIOException extends AbstractHttpUploadException {
+
+ private static final long serialVersionUID = 5940866318073349451L;
+ private final IOException wrappedIOException;
+
+ public HttpUploadIOException(long fileSize, Slot slot, IOException cause) {
+ super(fileSize, slot, "Unexpected error occurred during file upload, file size: " + fileSize
+ + ", put Url: " + slot.getPutUrl(), cause);
+ this.wrappedIOException = cause;
+ }
+
+ public IOException getCausingIOException() {
+ return this.wrappedIOException;
+ }
+
+ }
+}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java
index 3f98a5056..338d519aa 100644
--- a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java
@@ -49,6 +49,8 @@ import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
+import org.jivesoftware.smackx.httpfileupload.AbstractHttpUploadException.HttpUploadErrorException;
+import org.jivesoftware.smackx.httpfileupload.AbstractHttpUploadException.HttpUploadIOException;
import org.jivesoftware.smackx.httpfileupload.UploadService.Version;
import org.jivesoftware.smackx.httpfileupload.element.Slot;
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
@@ -494,11 +496,12 @@ public final class HttpFileUploadManager extends Manager {
case HttpURLConnection.HTTP_NO_CONTENT:
break;
default:
- throw new IOException("Error response " + status + " from server during file upload: "
- + urlConnection.getResponseMessage() + ", file size: " + fileSize + ", put URL: "
- + putUrl);
+ throw new HttpUploadErrorException(status, urlConnection.getResponseMessage(), fileSize, slot);
}
}
+ catch (IOException e) {
+ throw new HttpUploadIOException(fileSize, slot, e);
+ }
finally {
urlConnection.disconnect();
}
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 b0d43e117..546e0d79c 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
@@ -47,15 +47,17 @@ import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.commands.AdHocCommandManager;
import org.jivesoftware.smackx.commands.RemoteCommand;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
+import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.disco.packet.DiscoverItems;
import org.jivesoftware.smackx.forward.packet.Forwarded;
-import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs;
+import org.jivesoftware.smackx.mam.element.MamElementFactory;
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;
import org.jivesoftware.smackx.mam.element.MamQueryIQ;
+import org.jivesoftware.smackx.mam.element.MamVersion;
import org.jivesoftware.smackx.mam.filter.MamResultFilter;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.rsm.packet.RSMSet;
@@ -225,6 +227,8 @@ public final class MamManager extends Manager {
private final AdHocCommandManager adHocCommandManager;
+ private MamVersion mamVersion = null;
+
private MamManager(XMPPConnection connection, Jid archiveAddress) {
super(connection);
this.archiveAddress = archiveAddress;
@@ -250,6 +254,52 @@ public final class MamManager extends Manager {
return archiveAddress;
}
+ /**
+ * Returns the MAM namespace used by this {@link MamManager}. If the archive does not support any MAM namespace
+ * supported by Smack, null is returned.
+ *
+ * @return the MAM namespace used by this manager, null if MAM is not supported
+ * @throws NoResponseException if there was no response from the remote entity.
+ * @throws XMPPErrorException if there was an XMPP error returned.
+ * @throws NotConnectedException if the XMPP connection is not connected.
+ * @throws InterruptedException if the calling thread was interrupted.
+ */
+ public String getMamNamespace() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException {
+ MamVersion mamVersion = getSupportedMamVersionOrNull();
+ return mamVersion == null ? null : mamVersion.getNamespace();
+ }
+
+ private MamVersion getSupportedMamVersionOrNull() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException {
+ if (mamVersion != null) {
+ return mamVersion;
+ }
+
+ DiscoverInfo info = serviceDiscoveryManager.discoverInfo(getArchiveAddress());
+
+ // Enum values are always returned the order they are declared (see https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.3).
+ // We pick the first version supported by the server.
+ for (MamVersion v : MamVersion.values()) {
+ if (info.containsFeature(v.getNamespace())) {
+ mamVersion = v;
+ break;
+ }
+ }
+
+ return mamVersion;
+ }
+
+ private MamVersion getSupportedMamVersionOrThrow() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException {
+ MamVersion mamVersion = getSupportedMamVersionOrNull();
+ if (mamVersion == null) {
+ throw new UnsupportedOperationException("Message Archive Management is not supported by " + getArchiveAddress());
+ }
+ return mamVersion;
+ }
+
+ private MamElementFactory getElementFactory() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException {
+ return getSupportedMamVersionOrThrow().newElementFactory();
+ }
+
public static final class MamQueryArgs {
private final String node;
@@ -275,11 +325,11 @@ public final class MamManager extends Manager {
private DataForm dataForm;
- DataForm getDataForm() {
+ DataForm getDataForm(MamVersion version) {
if (dataForm != null) {
return dataForm;
}
- DataForm.Builder dataFormBuilder = getNewMamForm();
+ DataForm.Builder dataFormBuilder = getNewMamForm(version);
dataFormBuilder.addFields(formFields.values());
dataForm = dataFormBuilder.build();
return dataForm;
@@ -472,9 +522,9 @@ public final class MamManager extends Manager {
NotConnectedException, NotLoggedInException, InterruptedException {
String queryId = StringUtils.secureUniqueRandomString();
String node = mamQueryArgs.node;
- DataForm dataForm = mamQueryArgs.getDataForm();
+ DataForm dataForm = mamQueryArgs.getDataForm(mamVersion);
- MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, node, dataForm);
+ MamQueryIQ mamQueryIQ = getElementFactory().newQueryIQ(queryId, node, dataForm);
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setTo(archiveAddress);
@@ -530,7 +580,7 @@ public final class MamManager extends Manager {
throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException, NotLoggedInException {
String queryId = StringUtils.secureUniqueRandomString();
- MamQueryIQ mamQueryIq = new MamQueryIQ(queryId, node, null);
+ MamQueryIQ mamQueryIq = getElementFactory().newQueryIQ(queryId, node, null);
mamQueryIq.setTo(archiveAddress);
MamQueryIQ mamResponseQueryIq = connection().sendIqRequestAndWaitForResponse(mamQueryIq);
@@ -592,7 +642,7 @@ public final class MamManager extends Manager {
private List page(RSMSet requestRsmSet) throws NoResponseException, XMPPErrorException,
NotConnectedException, NotLoggedInException, InterruptedException {
String queryId = StringUtils.secureUniqueRandomString();
- MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, node, form);
+ MamQueryIQ mamQueryIQ = getElementFactory().newQueryIQ(queryId, node, form);
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setTo(archiveAddress);
mamQueryIQ.addExtension(requestRsmSet);
@@ -696,9 +746,7 @@ public final class MamManager extends Manager {
* @see XEP-0313 § 7. Determining support
*/
public boolean isSupported() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- // Note that this may return 'null' but SDM's supportsFeature() does the right thing™ then.
- Jid archiveAddress = getArchiveAddress();
- return serviceDiscoveryManager.supportsFeature(archiveAddress, MamElements.NAMESPACE);
+ return getSupportedMamVersionOrNull() != null;
}
public boolean isAdvancedConfigurationSupported() throws InterruptedException, XMPPException, SmackException {
@@ -720,8 +768,8 @@ public final class MamManager extends Manager {
throw new SmackException.FeatureNotSupportedException(ADVANCED_CONFIG_NODE, archiveAddress);
}
- private static DataForm.Builder getNewMamForm() {
- FormField field = FormField.buildHiddenFormType(MamElements.NAMESPACE);
+ private static DataForm.Builder getNewMamForm(MamVersion version) {
+ FormField field = FormField.buildHiddenFormType(version.getNamespace());
DataForm.Builder form = DataForm.builder();
form.addField(field);
return form;
@@ -765,7 +813,7 @@ public final class MamManager extends Manager {
*/
public MamPrefsResult retrieveArchivingPreferences() throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
- MamPrefsIQ mamPrefIQ = new MamPrefsIQ();
+ MamPrefsIQ mamPrefIQ = getElementFactory().newPrefsIQ();
return queryMamPrefs(mamPrefIQ);
}
@@ -830,6 +878,7 @@ public final class MamManager extends Manager {
public static final class MamPrefs {
private final List alwaysJids;
private final List neverJids;
+ private final MamVersion mamVersion;
private DefaultBehavior defaultBehavior;
private MamPrefs(MamPrefsResult mamPrefsResult) {
@@ -837,6 +886,7 @@ public final class MamManager extends Manager {
this.alwaysJids = new ArrayList<>(mamPrefsIq.getAlwaysJids());
this.neverJids = new ArrayList<>(mamPrefsIq.getNeverJids());
this.defaultBehavior = mamPrefsIq.getDefault();
+ this.mamVersion = MamVersion.fromNamespace(mamPrefsIq.getNamespace());
}
public void setDefaultBehavior(DefaultBehavior defaultBehavior) {
@@ -856,7 +906,7 @@ public final class MamManager extends Manager {
}
private MamPrefsIQ constructMamPrefsIq() {
- return new MamPrefsIQ(alwaysJids, neverJids, defaultBehavior);
+ return mamVersion.newElementFactory().newPrefsIQ(alwaysJids, neverJids, defaultBehavior);
}
}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElementFactory.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElementFactory.java
new file mode 100644
index 000000000..6b0017bbc
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElementFactory.java
@@ -0,0 +1,94 @@
+/**
+ *
+ * Copyright © 2016-2021 Florian Schmaus and Frank Matheron
+ *
+ * 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.mam.element;
+
+import java.util.List;
+
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.xml.XmlPullParser;
+import org.jivesoftware.smackx.forward.packet.Forwarded;
+import org.jivesoftware.smackx.rsm.packet.RSMSet;
+import org.jivesoftware.smackx.xdata.packet.DataForm;
+
+import org.jxmpp.jid.Jid;
+
+/**
+ * Factory that creates MAM objects.
+ *
+ * @since 4.5.0
+ */
+public interface MamElementFactory {
+
+ /**
+ * Creates a new {@link MamElementFactory} for the parser based on the namespace of the parser.
+ * @param parser the XML parser to retrieve the MAM namespace from
+ * @return the factory suitable for the MAM namespace
+ */
+ static MamElementFactory forParser(XmlPullParser parser) {
+ String namespace = parser.getNamespace();
+ return MamVersion.fromNamespace(namespace).newElementFactory();
+ }
+
+ /**
+ * Create a MAM result extension class.
+ *
+ * @param queryId id of the query
+ * @param id the message's archive UID
+ * @param forwarded the original message as it was received
+ * @return the result extension
+ */
+ MamElements.MamResultExtension newResultExtension(String queryId, String id, Forwarded forwarded);
+
+ /**
+ * Create a MAM fin IQ class.
+ *
+ * @param queryId id of the query
+ * @param rsmSet the RSM set included in the {@code }
+ * @param complete true if the results returned by the server are complete (no further paging in needed)
+ * @param stable false if the results returned by the sever are unstable (e.g. they might later change in sequence or content)
+ * @return the fin IQ
+ */
+ MamFinIQ newFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable);
+
+ /**
+ * Create a new MAM preferences IQ.
+ *
+ * @param alwaysJids JIDs for which all messages are archived by default
+ * @param neverJids JIDs for which messages are never archived
+ * @param defaultBehavior default archive behavior
+ * @return the prefs IQ
+ */
+ MamPrefsIQ newPrefsIQ(List alwaysJids, List neverJids, MamPrefsIQ.DefaultBehavior defaultBehavior);
+
+ /**
+ * Construct a new MAM {@code } IQ retrieval request (IQ type 'get').
+ *
+ * @return the prefs IQ
+ */
+ MamPrefsIQ newPrefsIQ();
+
+ /**
+ * Create a new MAM Query IQ.
+ *
+ * @param queryId id of the query
+ * @param node pubsub node id when querying a pubsub node, null when not querying a pubsub node
+ * @param dataForm the dataform containing the query parameters
+ * @return the query IQ
+ */
+ MamQueryIQ newQueryIQ(String queryId, String node, DataForm dataForm);
+
+}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java
index 24c0753a6..6e3d7e982 100644
--- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java
@@ -18,15 +18,13 @@ package org.jivesoftware.smackx.mam.element;
import java.util.List;
-import javax.xml.namespace.QName;
-
import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.MessageView;
+import org.jivesoftware.smack.packet.XmlElement;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
-
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jxmpp.jid.Jid;
@@ -41,8 +39,6 @@ import org.jxmpp.jid.Jid;
*/
public class MamElements {
- public static final String NAMESPACE = "urn:xmpp:mam:2";
-
/**
* MAM result extension class.
*
@@ -50,18 +46,13 @@ public class MamElements {
* Archive Management
*
*/
- public static class MamResultExtension implements ExtensionElement {
+ public abstract static class MamResultExtension implements ExtensionElement {
/**
* result element.
*/
public static final String ELEMENT = "result";
- /**
- * The qualified name of the MAM result extension element.
- */
- public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
-
/**
* id of the result.
*/
@@ -77,20 +68,27 @@ public class MamElements {
*/
private String queryId;
+ protected final MamVersion version;
+
/**
* MAM result extension constructor.
*
+ * @param version TODO javadoc me please
* @param queryId TODO javadoc me please
* @param id TODO javadoc me please
* @param forwarded TODO javadoc me please
*/
- public MamResultExtension(String queryId, String id, Forwarded forwarded) {
+ public MamResultExtension(MamVersion version, String queryId, String id, Forwarded forwarded) {
if (StringUtils.isEmpty(id)) {
throw new IllegalArgumentException("id must not be null or empty");
}
if (forwarded == null) {
throw new IllegalArgumentException("forwarded must no be null");
}
+ if (version == null) {
+ throw new IllegalArgumentException("version must not be null");
+ }
+ this.version = version;
this.id = id;
this.forwarded = forwarded;
this.queryId = queryId;
@@ -130,7 +128,7 @@ public class MamElements {
@Override
public final String getNamespace() {
- return NAMESPACE;
+ return version.getNamespace();
}
@Override
@@ -148,7 +146,13 @@ public class MamElements {
}
public static MamResultExtension from(MessageView message) {
- return message.getExtension(MamResultExtension.class);
+ for (XmlElement extension : message.getExtensions()) {
+ if (extension instanceof MamResultExtension) {
+ return (MamResultExtension) extension;
+ }
+ }
+
+ return null;
}
}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamFinIQ.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamFinIQ.java
index cd5b92669..91b2defbc 100644
--- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamFinIQ.java
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamFinIQ.java
@@ -35,11 +35,6 @@ public class MamFinIQ extends IQ {
*/
public static final String ELEMENT = "fin";
- /**
- * the IQ NAMESPACE.
- */
- public static final String NAMESPACE = MamElements.NAMESPACE;
-
/**
* RSM set.
*/
@@ -63,13 +58,14 @@ public class MamFinIQ extends IQ {
/**
* MamFinIQ constructor.
*
+ * @param version TODO javadoc me please
* @param queryId TODO javadoc me please
* @param rsmSet TODO javadoc me please
* @param complete TODO javadoc me please
* @param stable TODO javadoc me please
*/
- public MamFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable) {
- super(ELEMENT, NAMESPACE);
+ public MamFinIQ(MamVersion version, String queryId, RSMSet rsmSet, boolean complete, boolean stable) {
+ super(ELEMENT, version.getNamespace());
if (rsmSet == null) {
throw new IllegalArgumentException("rsmSet must not be null");
}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamPrefsIQ.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamPrefsIQ.java
index 743847914..e5898f4c3 100644
--- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamPrefsIQ.java
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamPrefsIQ.java
@@ -46,11 +46,6 @@ public class MamPrefsIQ extends IQ {
*/
public static final String ELEMENT = "prefs";
- /**
- * the IQ NAMESPACE.
- */
- public static final String NAMESPACE = MamElements.NAMESPACE;
-
/**
* list of always.
*/
@@ -68,9 +63,11 @@ public class MamPrefsIQ extends IQ {
/**
* Construct a new MAM {@code } IQ retrieval request (IQ type 'get').
+ *
+ * @param version TODO javadoc me please *
*/
- public MamPrefsIQ() {
- super(ELEMENT, NAMESPACE);
+ public MamPrefsIQ(MamVersion version) {
+ super(ELEMENT, version.getNamespace());
alwaysJids = null;
neverJids = null;
defaultBehavior = null;
@@ -79,12 +76,13 @@ public class MamPrefsIQ extends IQ {
/**
* MAM preferences IQ constructor.
*
+ * @param version TODO javadoc me please
* @param alwaysJids TODO javadoc me please
* @param neverJids TODO javadoc me please
* @param defaultBehavior TODO javadoc me please
*/
- public MamPrefsIQ(List alwaysJids, List neverJids, DefaultBehavior defaultBehavior) {
- super(ELEMENT, NAMESPACE);
+ public MamPrefsIQ(MamVersion version, List alwaysJids, List neverJids, DefaultBehavior defaultBehavior) {
+ super(ELEMENT, version.getNamespace());
setType(Type.set);
this.alwaysJids = alwaysJids;
this.neverJids = neverJids;
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java
index b41400505..3909b077c 100644
--- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java
@@ -35,11 +35,6 @@ public class MamQueryIQ extends IQ {
*/
public static final String ELEMENT = QUERY_ELEMENT;
- /**
- * the MAM query IQ NAMESPACE.
- */
- public static final String NAMESPACE = MamElements.NAMESPACE;
-
private final String queryId;
private final String node;
private final DataForm dataForm;
@@ -47,41 +42,45 @@ public class MamQueryIQ extends IQ {
/**
* MAM query IQ constructor.
*
+ * @param version TODO javadoc me please
* @param queryId TODO javadoc me please
*/
- public MamQueryIQ(String queryId) {
- this(queryId, null, null);
+ public MamQueryIQ(MamVersion version, String queryId) {
+ this(version, queryId, null, null);
setType(IQ.Type.get);
}
/**
* MAM query IQ constructor.
*
+ * @param version TODO javadoc me please
* @param form TODO javadoc me please
*/
- public MamQueryIQ(DataForm form) {
- this(null, null, form);
+ public MamQueryIQ(MamVersion version, DataForm form) {
+ this(version, null, null, form);
}
/**
* MAM query IQ constructor.
*
+ * @param version TODO javadoc me please
* @param queryId TODO javadoc me please
* @param form TODO javadoc me please
*/
- public MamQueryIQ(String queryId, DataForm form) {
- this(queryId, null, form);
+ public MamQueryIQ(MamVersion version, String queryId, DataForm form) {
+ this(version, queryId, null, form);
}
/**
* MAM query IQ constructor.
*
+ * @param version TODO javadoc me please
* @param queryId TODO javadoc me please
* @param node TODO javadoc me please
* @param dataForm TODO javadoc me please
*/
- public MamQueryIQ(String queryId, String node, DataForm dataForm) {
- super(ELEMENT, NAMESPACE);
+ public MamQueryIQ(MamVersion version, String queryId, String node, DataForm dataForm) {
+ super(ELEMENT, version.getNamespace());
this.queryId = queryId;
this.node = node;
this.dataForm = dataForm;
@@ -91,9 +90,9 @@ public class MamQueryIQ extends IQ {
if (formType == null) {
throw new IllegalArgumentException("If a data form is given it must posses a hidden form type field");
}
- if (!formType.equals(MamElements.NAMESPACE)) {
+ if (!formType.equals(version.getNamespace())) {
throw new IllegalArgumentException(
- "Value of the hidden form type field must be '" + MamElements.NAMESPACE + "'");
+ "Value of the hidden form type field must be '" + version.getNamespace() + "'");
}
addExtension(dataForm);
}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV1ElementFactory.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV1ElementFactory.java
new file mode 100644
index 000000000..e87f79ca7
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV1ElementFactory.java
@@ -0,0 +1,66 @@
+/**
+ *
+ * Copyright © 2016-2021 Florian Schmaus and Frank Matheron
+ *
+ * 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.mam.element;
+
+import java.util.List;
+import javax.xml.namespace.QName;
+
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smackx.forward.packet.Forwarded;
+import org.jivesoftware.smackx.rsm.packet.RSMSet;
+import org.jivesoftware.smackx.xdata.packet.DataForm;
+
+import org.jxmpp.jid.Jid;
+
+class MamV1ElementFactory implements MamElementFactory {
+
+ @Override
+ public MamElements.MamResultExtension newResultExtension(String queryId, String id, Forwarded forwarded) {
+ return new MamV1ResultExtension(queryId, id, forwarded);
+ }
+
+ @Override
+ public MamFinIQ newFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable) {
+ return new MamFinIQ(MamVersion.MAM1, queryId, rsmSet, complete, stable);
+ }
+
+ @Override
+ public MamPrefsIQ newPrefsIQ(List alwaysJids, List neverJids, MamPrefsIQ.DefaultBehavior defaultBehavior) {
+ return new MamPrefsIQ(MamVersion.MAM1, alwaysJids, neverJids, defaultBehavior);
+ }
+
+ @Override
+ public MamPrefsIQ newPrefsIQ() {
+ return new MamPrefsIQ(MamVersion.MAM1);
+ }
+
+ @Override
+ public MamQueryIQ newQueryIQ(String queryId, String node, DataForm dataForm) {
+ return new MamQueryIQ(MamVersion.MAM1, queryId, node, dataForm);
+ }
+
+ public static class MamV1ResultExtension extends MamElements.MamResultExtension {
+ /**
+ * The qualified name of the MAM result extension element.
+ */
+ public static final QName QNAME = new QName(MamVersion.MAM1.getNamespace(), ELEMENT);
+
+ MamV1ResultExtension(String queryId, String id, Forwarded forwarded) {
+ super(MamVersion.MAM1, queryId, id, forwarded);
+ }
+ }
+}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV2ElementFactory.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV2ElementFactory.java
new file mode 100644
index 000000000..a706f8632
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV2ElementFactory.java
@@ -0,0 +1,66 @@
+/**
+ *
+ * Copyright © 2016-2021 Florian Schmaus and Frank Matheron
+ *
+ * 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.mam.element;
+
+import java.util.List;
+import javax.xml.namespace.QName;
+
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smackx.forward.packet.Forwarded;
+import org.jivesoftware.smackx.rsm.packet.RSMSet;
+import org.jivesoftware.smackx.xdata.packet.DataForm;
+
+import org.jxmpp.jid.Jid;
+
+class MamV2ElementFactory implements MamElementFactory {
+
+ @Override
+ public MamElements.MamResultExtension newResultExtension(String queryId, String id, Forwarded forwarded) {
+ return new MamV2ResultExtension(queryId, id, forwarded);
+ }
+
+ @Override
+ public MamFinIQ newFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable) {
+ return new MamFinIQ(MamVersion.MAM2, queryId, rsmSet, complete, stable);
+ }
+
+ @Override
+ public MamPrefsIQ newPrefsIQ(List alwaysJids, List neverJids, MamPrefsIQ.DefaultBehavior defaultBehavior) {
+ return new MamPrefsIQ(MamVersion.MAM2, alwaysJids, neverJids, defaultBehavior);
+ }
+
+ @Override
+ public MamPrefsIQ newPrefsIQ() {
+ return new MamPrefsIQ(MamVersion.MAM2);
+ }
+
+ @Override
+ public MamQueryIQ newQueryIQ(String queryId, String node, DataForm dataForm) {
+ return new MamQueryIQ(MamVersion.MAM2, queryId, node, dataForm);
+ }
+
+ public static class MamV2ResultExtension extends MamElements.MamResultExtension {
+ /**
+ * The qualified name of the MAM result extension element.
+ */
+ public static final QName QNAME = new QName(MamVersion.MAM2.getNamespace(), ELEMENT);
+
+ MamV2ResultExtension(String queryId, String id, Forwarded forwarded) {
+ super(MamVersion.MAM2, queryId, id, forwarded);
+ }
+ }
+}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamVersion.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamVersion.java
new file mode 100644
index 000000000..50a7a211d
--- /dev/null
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamVersion.java
@@ -0,0 +1,69 @@
+/**
+ *
+ * Copyright © 2016-2021 Florian Schmaus and Frank Matheron
+ *
+ * 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.mam.element;
+
+/**
+ * MAM versions supported by Smack.
+ *
+ * @since 4.5.0
+ */
+public enum MamVersion {
+ // Note that the order in which the enum values are defined, is also the order in which we attempt to find a
+ // supported version. The versions should therefore be listed in order of newest to oldest, so that Smack prefers
+ // using a newer version over an older version.
+ MAM2("urn:xmpp:mam:2") {
+ @Override
+ public MamElementFactory newElementFactory() {
+ return new MamV2ElementFactory();
+ }
+ },
+ MAM1("urn:xmpp:mam:1") {
+ @Override
+ public MamElementFactory newElementFactory() {
+ return new MamV1ElementFactory();
+ }
+ };
+
+ private final String namespace;
+
+ MamVersion(String namespace) {
+ this.namespace = namespace;
+ }
+
+ /**
+ * Each MAM version is identified by its namespace. Returns the namespace for this MAM version.
+ * @return the namespace of the MAM version
+ */
+ public String getNamespace() {
+ return namespace;
+ }
+
+ /**
+ * Creates a new factory that creates IQ's and extension objects for this MAM version.
+ * @return the factory
+ */
+ public abstract MamElementFactory newElementFactory();
+
+ public static MamVersion fromNamespace(String namespace) {
+ for (MamVersion v : MamVersion.values()) {
+ if (v.namespace.equals(namespace)) {
+ return v;
+ }
+ }
+ throw new IllegalArgumentException("Unsupported namespace: " + namespace);
+ }
+}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamFinIQProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamFinIQProvider.java
index 1af384165..92be55c0b 100644
--- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamFinIQProvider.java
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamFinIQProvider.java
@@ -25,6 +25,7 @@ import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
+import org.jivesoftware.smackx.mam.element.MamElementFactory;
import org.jivesoftware.smackx.mam.element.MamFinIQ;
import org.jivesoftware.smackx.rsm.packet.RSMSet;
import org.jivesoftware.smackx.rsm.provider.RSMSetProvider;
@@ -41,6 +42,7 @@ public class MamFinIQProvider extends IQProvider {
@Override
public MamFinIQ parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
+ MamElementFactory elementFactory = MamElementFactory.forParser(parser);
String queryId = parser.getAttributeValue("", "queryid");
boolean complete = ParserUtils.getBooleanAttribute(parser, "complete", false);
boolean stable = ParserUtils.getBooleanAttribute(parser, "stable", true);
@@ -65,7 +67,7 @@ public class MamFinIQProvider extends IQProvider {
}
}
- return new MamFinIQ(queryId, rsmSet, complete, stable);
+ return elementFactory.newFinIQ(queryId, rsmSet, complete, stable);
}
}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java
index 2b4795036..d4d0b7c46 100644
--- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java
@@ -25,6 +25,7 @@ import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
+import org.jivesoftware.smackx.mam.element.MamElementFactory;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior;
@@ -41,19 +42,17 @@ import org.jxmpp.jid.impl.JidCreate;
*/
public class MamPrefsIQProvider extends IQProvider {
+ public static final MamPrefsIQProvider INSTANCE = new MamPrefsIQProvider();
+
@Override
public MamPrefsIQ parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException {
- String iqType = parser.getAttributeValue("", "type");
+ MamElementFactory elementFactory = MamElementFactory.forParser(parser);
String defaultBehaviorString = parser.getAttributeValue("", "default");
DefaultBehavior defaultBehavior = null;
if (defaultBehaviorString != null) {
defaultBehavior = DefaultBehavior.valueOf(defaultBehaviorString);
}
- if (iqType == null) {
- iqType = "result";
- }
-
List alwaysJids = null;
List neverJids = null;
@@ -82,7 +81,7 @@ public class MamPrefsIQProvider extends IQProvider {
}
}
- return new MamPrefsIQ(alwaysJids, neverJids, defaultBehavior);
+ return elementFactory.newPrefsIQ(alwaysJids, neverJids, defaultBehavior);
}
private static List iterateJids(XmlPullParser parser) throws XmlPullParserException, IOException {
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java
index d0971c5de..e6280a52d 100644
--- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java
@@ -24,6 +24,7 @@ import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
+import org.jivesoftware.smackx.mam.element.MamElementFactory;
import org.jivesoftware.smackx.mam.element.MamQueryIQ;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jivesoftware.smackx.xdata.provider.DataFormProvider;
@@ -41,6 +42,7 @@ public class MamQueryIQProvider extends IQProvider {
@Override
public MamQueryIQ parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
throws XmlPullParserException, IOException, SmackParsingException {
+ MamElementFactory elementFactory = MamElementFactory.forParser(parser);
DataForm dataForm = null;
String queryId = parser.getAttributeValue("", "queryid");
String node = parser.getAttributeValue("", "node");
@@ -68,7 +70,7 @@ public class MamQueryIQProvider extends IQProvider {
}
}
- return new MamQueryIQ(queryId, node, dataForm);
+ return elementFactory.newQueryIQ(queryId, node, dataForm);
}
}
diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java
index 21012f873..c870e5c87 100644
--- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java
+++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java
@@ -28,6 +28,7 @@ import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.forward.packet.Forwarded;
import org.jivesoftware.smackx.forward.provider.ForwardedProvider;
+import org.jivesoftware.smackx.mam.element.MamElementFactory;
import org.jivesoftware.smackx.mam.element.MamElements.MamResultExtension;
/**
@@ -43,6 +44,7 @@ public class MamResultProvider extends ExtensionElementProvider forwarded = null;
String queryId = parser.getAttributeValue("", "queryid");
String id = parser.getAttributeValue("", "id");
@@ -69,7 +71,7 @@ public class MamResultProvider extends ExtensionElementProviderurn:xmpp:mam:2
org.jivesoftware.smackx.mam.provider.MamResultProvider
+
+ prefs
+ urn:xmpp:mam:1
+ org.jivesoftware.smackx.mam.provider.MamPrefsIQProvider
+
+
+ query
+ urn:xmpp:mam:1
+ org.jivesoftware.smackx.mam.provider.MamQueryIQProvider
+
+
+ fin
+ urn:xmpp:mam:1
+ org.jivesoftware.smackx.mam.provider.MamFinIQProvider
+
+
+ result
+ urn:xmpp:mam:1
+ org.jivesoftware.smackx.mam.provider.MamResultProvider
+
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 212c37e89..b5c974dce 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
@@ -23,7 +23,7 @@ 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.mam.element.MamVersion;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.junit.jupiter.api.Test;
@@ -35,7 +35,7 @@ public class FiltersTest extends MamTest {
private static String getMamXMemberWith(List fieldsNames, List extends CharSequence> fieldsValues) {
String xml = "" + "" + ""
- + MamElements.NAMESPACE + "" + "";
+ + MamVersion.MAM2.getNamespace() + "" + "";
for (int i = 0; i < fieldsNames.size() && i < fieldsValues.size(); i++) {
xml += "" + "" + fieldsValues.get(i) + ""
@@ -51,7 +51,7 @@ public class FiltersTest extends MamTest {
Date date = new Date();
MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsSince(date).build();
- DataForm dataForm = mamQueryArgs.getDataForm();
+ DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2);
List fields = new ArrayList<>();
fields.add("start");
@@ -66,7 +66,7 @@ public class FiltersTest extends MamTest {
Date date = new Date();
MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsBefore(date).build();
- DataForm dataForm = mamQueryArgs.getDataForm();
+ DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2);
List fields = new ArrayList<>();
fields.add("end");
@@ -81,7 +81,7 @@ public class FiltersTest extends MamTest {
Jid jid = JidTestUtil.BARE_JID_1;
MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsToJid(jid).build();
- DataForm dataForm = mamQueryArgs.getDataForm();
+ DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2);
List fields = new ArrayList<>();
fields.add("with");
diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamPrefIQProviderTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamPrefIQProviderTest.java
index cd5d4c0ca..956d2057b 100644
--- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamPrefIQProviderTest.java
+++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamPrefIQProviderTest.java
@@ -19,54 +19,62 @@ package org.jivesoftware.smackx.mam;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.IOException;
import java.util.List;
import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.parsing.SmackParsingException;
+import org.jivesoftware.smack.test.util.SmackTestUtil;
+import org.jivesoftware.smack.test.util.SmackTestUtil.XmlPullParserKind;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
+import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ;
import org.jivesoftware.smackx.mam.provider.MamPrefsIQProvider;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
import org.jxmpp.jid.Jid;
public class MamPrefIQProviderTest extends MamTest {
- private static final String exampleMamPrefsIQ1 = "" + ""
+ private static final String exampleMamPrefsIQ1 = ""
+ "" + "romeo@montague.lit" + "" + ""
- + "montague@montague.lit" + "" + "" + "";
+ + "montague@montague.lit" + "" + "";
- private static final String exampleMamPrefsIQ2 = "" + ""
+ private static final String exampleMamPrefsIQ2 = ""
+ "" + "romeo@montague.lit" + "montague@montague.lit" + ""
- + "" + "" + "" + "";
+ + "" + "" + "";
- private static final String exampleMamPrefsIQ3 = "" + "" + ""
- + "";
+ private static final String exampleMamPrefsIQ3 = "" + "";
private static final String exampleMamPrefsResultIQ = ""
+ "" + "" + "romeo@montague.lit"
+ "" + "" + "sarasa@montague.lit" + "montague@montague.lit"
+ "" + "" + "";
- @Test
- public void checkMamPrefsIQProvider() throws Exception {
- XmlPullParser parser1 = PacketParserUtils.getParserFor(exampleMamPrefsIQ1);
- MamPrefsIQ mamPrefIQ1 = new MamPrefsIQProvider().parse(parser1);
+ @ParameterizedTest
+ @EnumSource(value = SmackTestUtil.XmlPullParserKind.class)
+ public void checkMamPrefsIQProvider(XmlPullParserKind parserKind)
+ throws XmlPullParserException, IOException, SmackParsingException {
+ XmlPullParser parser1 = SmackTestUtil.getParserFor(exampleMamPrefsIQ1, parserKind);
+ MamPrefsIQ mamPrefIQ1 = MamPrefsIQProvider.INSTANCE.parse(parser1);
assertEquals(IQ.Type.set, mamPrefIQ1.getType());
assertEquals(mamPrefIQ1.getAlwaysJids().get(0).toString(), "romeo@montague.lit");
assertEquals(mamPrefIQ1.getNeverJids().get(0).toString(), "montague@montague.lit");
- XmlPullParser parser2 = PacketParserUtils.getParserFor(exampleMamPrefsIQ2);
- MamPrefsIQ mamPrefIQ2 = new MamPrefsIQProvider().parse(parser2);
+ XmlPullParser parser2 = SmackTestUtil.getParserFor(exampleMamPrefsIQ2, parserKind);
+ MamPrefsIQ mamPrefIQ2 = MamPrefsIQProvider.INSTANCE.parse(parser2);
assertEquals(IQ.Type.set, mamPrefIQ2.getType());
assertEquals(mamPrefIQ2.getAlwaysJids().get(0).toString(), "romeo@montague.lit");
assertEquals(mamPrefIQ2.getAlwaysJids().get(1).toString(), "montague@montague.lit");
assertTrue(mamPrefIQ2.getNeverJids().isEmpty());
- XmlPullParser parser3 = PacketParserUtils.getParserFor(exampleMamPrefsIQ3);
- MamPrefsIQ mamPrefIQ3 = new MamPrefsIQProvider().parse(parser3);
+ XmlPullParser parser3 = SmackTestUtil.getParserFor(exampleMamPrefsIQ3, parserKind);
+ MamPrefsIQ mamPrefIQ3 = MamPrefsIQProvider.INSTANCE.parse(parser3);
assertEquals(IQ.Type.set, mamPrefIQ3.getType());
}
diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java
index 33e9df1a3..b8e6727d5 100644
--- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java
+++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java
@@ -23,6 +23,7 @@ import org.jivesoftware.smack.DummyConnection;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.test.util.SmackTestSuite;
+import org.jivesoftware.smackx.mam.element.MamVersion;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.junit.jupiter.api.BeforeAll;
@@ -47,9 +48,9 @@ public class MamTest extends SmackTestSuite {
protected DataForm getNewMamForm() throws NoSuchMethodException, SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
- Method methodGetNewMamForm = MamManager.class.getDeclaredMethod("getNewMamForm");
+ Method methodGetNewMamForm = MamManager.class.getDeclaredMethod("getNewMamForm", MamVersion.class);
methodGetNewMamForm.setAccessible(true);
- DataForm.Builder dataFormBuilder = (DataForm.Builder) methodGetNewMamForm.invoke(mamManager);
+ DataForm.Builder dataFormBuilder = (DataForm.Builder) methodGetNewMamForm.invoke(mamManager, MamVersion.MAM2);
return dataFormBuilder.build();
}
diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java
index 9a3a4e61a..7d7a26966 100644
--- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java
+++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java
@@ -22,6 +22,7 @@ import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smackx.mam.element.MamQueryIQ;
+import org.jivesoftware.smackx.mam.element.MamVersion;
import org.jivesoftware.smackx.rsm.packet.RSMSet;
import org.jivesoftware.smackx.xdata.packet.DataForm;
@@ -40,7 +41,7 @@ public class PagingTest extends MamTest {
int max = 10;
RSMSet rsmSet = new RSMSet(max);
- MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm);
+ MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId, dataForm);
mamQueryIQ.setStanzaId("sarasa");
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.addExtension(rsmSet);
diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PreferencesTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PreferencesTest.java
index 2e80871bc..5b7aa81a7 100644
--- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PreferencesTest.java
+++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PreferencesTest.java
@@ -23,9 +23,9 @@ import java.util.List;
import org.jivesoftware.smack.packet.StreamOpen;
-import org.jivesoftware.smackx.mam.element.MamElements;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ;
import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior;
+import org.jivesoftware.smackx.mam.element.MamVersion;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.Jid;
@@ -33,16 +33,16 @@ import org.jxmpp.jid.impl.JidCreate;
public class PreferencesTest {
- private static final String retrievePrefsStanzaExample = "" + "" + "" + "";
- private static final String updatePrefsStanzaExample = "" + "" + "" + "" + "romeo@montague.lit" + "other@montague.lit"
+ "" + "" + "montague@montague.lit" + "" + "" + "";
@Test
public void checkRetrievePrefsStanza() throws Exception {
- MamPrefsIQ mamPrefIQ = new MamPrefsIQ();
+ MamPrefsIQ mamPrefIQ = MamVersion.MAM2.newElementFactory().newPrefsIQ();
mamPrefIQ.setStanzaId("sarasa");
assertEquals(mamPrefIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), retrievePrefsStanzaExample);
}
@@ -56,7 +56,7 @@ public class PreferencesTest {
List neverJids = new ArrayList<>();
neverJids.add(JidCreate.from("montague@montague.lit"));
- MamPrefsIQ mamPrefIQ = new MamPrefsIQ(alwaysJids, neverJids, DefaultBehavior.roster);
+ MamPrefsIQ mamPrefIQ = MamVersion.MAM2.newElementFactory().newPrefsIQ(alwaysJids, neverJids, DefaultBehavior.roster);
mamPrefIQ.setStanzaId("sarasa");
assertEquals(mamPrefIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), updatePrefsStanzaExample);
}
diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java
index 3b378a516..85ac63a8f 100644
--- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java
+++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java
@@ -29,9 +29,9 @@ import org.jivesoftware.smack.packet.StreamOpen;
import org.jivesoftware.smackx.delay.packet.DelayInformation;
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.MamQueryIQ;
+import org.jivesoftware.smackx.mam.element.MamVersion;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.junit.jupiter.api.Test;
@@ -41,7 +41,7 @@ public class QueryArchiveTest extends MamTest {
private static final String mamSimpleQueryIQ = "" + ""
+ "" + "" + ""
- + MamElements.NAMESPACE + "" + "" + "" + "" + "";
+ + MamVersion.MAM2.getNamespace() + "" + "" + "" + "" + "";
private static final String mamQueryResultExample = ""
+ ""
@@ -54,7 +54,7 @@ public class QueryArchiveTest extends MamTest {
@Test
public void checkMamQueryIQ() throws Exception {
DataForm dataForm = getNewMamForm();
- MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm);
+ MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId, dataForm);
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setStanzaId("sarasa");
assertEquals(mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), mamSimpleQueryIQ);
@@ -80,7 +80,7 @@ public class QueryArchiveTest extends MamTest {
Forwarded forwarded = new Forwarded<>(forwardedMessage, delay);
- message.addExtension(new MamResultExtension("g27", "34482-21985-73620", forwarded));
+ message.addExtension(MamVersion.MAM2.newElementFactory().newResultExtension("g27", "34482-21985-73620", forwarded));
assertEquals(mamQueryResultExample, message.toXML(StreamOpen.CLIENT_NAMESPACE).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 893b78a5f..4089a3296 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
@@ -22,8 +22,8 @@ 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.mam.element.MamVersion;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.junit.jupiter.api.Test;
@@ -32,13 +32,13 @@ public class ResultsLimitTest extends MamTest {
private static final String resultsLimitStanza = "" + ""
+ "" + "" + ""
- + MamElements.NAMESPACE + "" + "" + "" + ""
+ + MamVersion.MAM2.getNamespace() + "" + "" + "" + ""
+ "10" + "" + "" + "";
@Test
public void checkResultsLimit() throws Exception {
DataForm dataForm = getNewMamForm();
- MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm);
+ MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId, dataForm);
mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setStanzaId("sarasa");
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 f973fa719..3fb2c3928 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
@@ -22,8 +22,8 @@ import static org.junit.jupiter.api.Assertions.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.mam.element.MamVersion;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.packet.DataForm;
@@ -32,18 +32,18 @@ import org.jxmpp.jid.JidTestUtil;
public class RetrieveFormFieldsTest extends MamTest {
- private static final String retrieveFormFieldStanza = "" + "" + "" + "";
private static final String additionalFieldsStanza = "" + ""
- + "" + MamElements.NAMESPACE + "" + ""
+ + "" + MamVersion.MAM2.getNamespace() + "" + ""
+ "" + "Hi" + ""
+ "" + "one@exampleone.org" + ""
+ "";
@Test
public void checkRetrieveFormFieldsStanza() throws Exception {
- MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId);
+ MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId);
mamQueryIQ.setStanzaId("sarasa");
assertEquals(retrieveFormFieldStanza, mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString());
@@ -63,7 +63,7 @@ public class RetrieveFormFieldsTest extends MamTest {
.withAdditionalFormField(field1)
.withAdditionalFormField(field2)
.build();
- DataForm dataForm = mamQueryArgs.getDataForm();
+ DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2);
String dataFormResult = dataForm.toXML().toString();
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java
index 532ac9a11..008c941c6 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java
@@ -153,7 +153,7 @@ public class DataPacketExtension implements ExtensionElement {
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
- XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this));
+ XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this, enclosingNamespace));
xml.closeElement(this);
return xml;
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java
index 12be65fcb..66e221c49 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java
@@ -615,9 +615,7 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream
if (annouceLocalStreamHost) {
// add local proxy on first position if exists
List localProxies = getLocalStreamHost();
- if (localProxies != null) {
- streamHosts.addAll(localProxies);
- }
+ streamHosts.addAll(localProxies);
}
// query SOCKS5 proxies for network settings
@@ -652,12 +650,14 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream
/**
* Returns the stream host information of the local SOCKS5 proxy containing the IP address and
- * the port or null if local SOCKS5 proxy is not running.
+ * the port. The returned list may be empty if the local SOCKS5 proxy is not running.
*
- * @return the stream host information of the local SOCKS5 proxy or null if local SOCKS5 proxy
- * is not running
+ * @return the stream host information of the local SOCKS5 proxy
*/
public List getLocalStreamHost() {
+ // Ensure that the local SOCKS5 proxy is running (if enabled).
+ Socks5Proxy.getSocks5Proxy();
+
List streamHosts = new ArrayList<>();
XMPPConnection connection = connection();
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java
index 034d0f9dd..7f481ab21 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java
@@ -700,7 +700,7 @@ public final class EntityCapsManager extends Manager {
for (FormField f : fs) {
sb.append(f.getFieldName());
sb.append('<');
- formFieldValuesToCaps(f.getRawValues(), sb);
+ formFieldValuesToCaps(f.getRawValueCharSequences(), sb);
}
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java
index b37209ad4..20714db77 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java
@@ -921,6 +921,10 @@ public final class ServiceDiscoveryManager extends Manager {
return entityCapabilitiesChangedListeners.add(entityCapabilitiesChangedListener);
}
+ public boolean removeEntityCapabilitiesChangedListener(EntityCapabilitiesChangedListener entityCapabilitiesChangedListener) {
+ return entityCapabilitiesChangedListeners.remove(entityCapabilitiesChangedListener);
+ }
+
private static final int RENEW_ENTITY_CAPS_DELAY_MILLIS = 25;
private ScheduledAction renewEntityCapsScheduledAction;
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java
index b4d3f7554..abe18f630 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java
@@ -258,6 +258,14 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView {
return features.contains(new Feature(feature));
}
+ public static boolean nullSafeContainsFeature(DiscoverInfo discoverInfo, CharSequence feature) {
+ if (discoverInfo == null) {
+ return false;
+ }
+
+ return discoverInfo.containsFeature(feature);
+ }
+
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
xml.optAttribute("node", getNode());
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java
index 356cf52fb..5dda27842 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java
@@ -68,6 +68,12 @@ public class FormFieldRegistry {
}
}
+ public static void register(String formType, FormField.Type fieldType, String... fieldNames) {
+ for (String fieldName : fieldNames) {
+ register(formType, fieldName, fieldType);
+ }
+ }
+
public static void register(String formType, String fieldName, FormField.Type fieldType) {
StringUtils.requireNotNullNorEmpty(fieldName, "fieldName must be provided");
Objects.requireNonNull(fieldType);
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java
index 000765ec9..df08bace6 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java
@@ -53,7 +53,7 @@ public class JingleUtil {
JingleContentDescription description,
JingleContentTransport transport) {
- Jingle.Builder jb = Jingle.getBuilder();
+ Jingle.Builder jb = Jingle.builder(connection);
jb.setAction(JingleAction.session_initiate)
.setSessionId(sessionId)
.setInitiator(connection.getUser());
@@ -118,7 +118,7 @@ public class JingleUtil {
JingleContentDescription description,
JingleContentTransport transport) {
- Jingle.Builder jb = Jingle.getBuilder();
+ Jingle.Builder jb = Jingle.builder(connection);
jb.setResponder(connection.getUser())
.setAction(JingleAction.session_accept)
.setSessionId(sessionId);
@@ -153,7 +153,7 @@ public class JingleUtil {
}
public Jingle createSessionTerminate(FullJid recipient, String sessionId, JingleReason reason) {
- Jingle.Builder jb = Jingle.getBuilder();
+ Jingle.Builder jb = Jingle.builder(connection);
jb.setAction(JingleAction.session_terminate)
.setSessionId(sessionId)
.setReason(reason);
@@ -232,7 +232,7 @@ public class JingleUtil {
public Jingle createSessionTerminateContentCancel(FullJid recipient, String sessionId,
JingleContent.Creator contentCreator, String contentName) {
- Jingle.Builder jb = Jingle.getBuilder();
+ Jingle.Builder jb = Jingle.builder(connection);
jb.setAction(JingleAction.session_terminate)
.setSessionId(sessionId);
@@ -314,7 +314,7 @@ public class JingleUtil {
}
public Jingle createSessionPing(FullJid recipient, String sessionId) {
- Jingle.Builder jb = Jingle.getBuilder();
+ Jingle.Builder jb = Jingle.builder(connection);
jb.setSessionId(sessionId)
.setAction(JingleAction.session_info);
@@ -343,7 +343,7 @@ public class JingleUtil {
public Jingle createTransportReplace(FullJid recipient, FullJid initiator, String sessionId,
JingleContent.Creator contentCreator, String contentName,
JingleContentTransport transport) {
- Jingle.Builder jb = Jingle.getBuilder();
+ Jingle.Builder jb = Jingle.builder(connection);
jb.setInitiator(initiator)
.setSessionId(sessionId)
.setAction(JingleAction.transport_replace);
@@ -370,7 +370,7 @@ public class JingleUtil {
public Jingle createTransportAccept(FullJid recipient, FullJid initiator, String sessionId,
JingleContent.Creator contentCreator, String contentName,
JingleContentTransport transport) {
- Jingle.Builder jb = Jingle.getBuilder();
+ Jingle.Builder jb = Jingle.builder(connection);
jb.setAction(JingleAction.transport_accept)
.setInitiator(initiator)
.setSessionId(sessionId);
@@ -397,7 +397,7 @@ public class JingleUtil {
public Jingle createTransportReject(FullJid recipient, FullJid initiator, String sessionId,
JingleContent.Creator contentCreator, String contentName,
JingleContentTransport transport) {
- Jingle.Builder jb = Jingle.getBuilder();
+ Jingle.Builder jb = Jingle.builder(connection);
jb.setAction(JingleAction.transport_reject)
.setInitiator(initiator)
.setSessionId(sessionId);
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java
index 40acf187a..7598d1894 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2003-2007 Jive Software, 2014-2017 Florian Schmaus
+ * Copyright 2003-2007 Jive Software, 2014-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,11 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.IqBuilder;
+import org.jivesoftware.smack.packet.IqData;
+import org.jivesoftware.smack.packet.id.StandardStanzaIdSource;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
@@ -65,9 +69,9 @@ public final class Jingle extends IQ {
private final List contents;
- private Jingle(String sessionId, JingleAction action, FullJid initiator, FullJid responder, JingleReason reason,
+ private Jingle(Builder builder, String sessionId, JingleAction action, FullJid initiator, FullJid responder, JingleReason reason,
List contents) {
- super(ELEMENT, NAMESPACE);
+ super(builder, ELEMENT, NAMESPACE);
this.sessionId = StringUtils.requireNotNullNorEmpty(sessionId, "Jingle session ID must not be null");
this.action = Objects.requireNonNull(action, "Jingle action must not be null");
this.initiator = initiator;
@@ -169,11 +173,31 @@ public final class Jingle extends IQ {
return xml;
}
+ /**
+ * Deprecated, do not use.
+ *
+ * @return a builder.
+ * @deprecated use {@link #builder(XMPPConnection)} instead.
+ */
+ @Deprecated
+ // TODO: Remove in Smack 4.6.
public static Builder getBuilder() {
- return new Builder();
+ return builder(StandardStanzaIdSource.DEFAULT.getNewStanzaId());
}
- public static final class Builder {
+ public static Builder builder(XMPPConnection connection) {
+ return new Builder(connection);
+ }
+
+ public static Builder builder(IqData iqData) {
+ return new Builder(iqData);
+ }
+
+ public static Builder builder(String stanzaId) {
+ return new Builder(stanzaId);
+ }
+
+ public static final class Builder extends IqBuilder {
private String sid;
private JingleAction action;
@@ -186,7 +210,16 @@ public final class Jingle extends IQ {
private List contents;
- private Builder() {
+ Builder(IqData iqCommon) {
+ super(iqCommon);
+ }
+
+ Builder(XMPPConnection connection) {
+ super(connection);
+ }
+
+ Builder(String stanzaId) {
+ super(stanzaId);
}
public Builder setSessionId(String sessionId) {
@@ -228,8 +261,14 @@ public final class Jingle extends IQ {
return this;
}
+ @Override
public Jingle build() {
- return new Jingle(sid, action, initiator, responder, reason, contents);
+ return new Jingle(this, sid, action, initiator, responder, reason, contents);
+ }
+
+ @Override
+ public Builder getThis() {
+ return this;
}
}
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java
index bfc085f60..ab5680c1d 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java
@@ -145,6 +145,11 @@ public final class JingleContent implements XmlElement {
xml.optAttribute(DISPOSITION_ATTRIBUTE_NAME, disposition);
xml.attribute(NAME_ATTRIBUTE_NAME, name);
xml.optAttribute(SENDERS_ATTRIBUTE_NAME, senders);
+
+ if (description == null && transport == null) {
+ return xml.closeEmptyElement();
+ }
+
xml.rightAngleBracket();
xml.optAppend(description);
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java
index 0f0c1d759..5e31244ca 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2017-2021 Florian Schmaus
+ * Copyright 2017-2022 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@ public class JingleReason implements XmlElement {
public static final String ELEMENT = "reason";
public static final String NAMESPACE = Jingle.NAMESPACE;
+ public static final String TEXT_ELEMENT = "text";
public static AlternativeSession AlternativeSession(String sessionId) {
return new AlternativeSession(sessionId);
@@ -105,9 +106,17 @@ public class JingleReason implements XmlElement {
}
protected final Reason reason;
+ private final String text;
+ private final XmlElement element;
public JingleReason(Reason reason) {
+ this(reason, null, null);
+ }
+
+ public JingleReason(Reason reason, String text, XmlElement element) {
this.reason = reason;
+ this.text = text;
+ this.element = element;
}
@Override
@@ -120,12 +129,34 @@ public class JingleReason implements XmlElement {
return NAMESPACE;
}
+ /**
+ * An optional text that provides human-readable information about the reason for the action.
+ *
+ * @return a human-readable text with information regarding this reason or null
.
+ * @since 4.4.5
+ */
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * An optional element that provides more detailed machine-readable information about the reason for the action.
+ *
+ * @return an element with machine-readable information about this reason or null
.
+ * @since 4.4.5
+ */
+ public XmlElement getElement() {
+ return element;
+ }
+
@Override
public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingXmlEnvironment);
xml.rightAngleBracket();
xml.emptyElement(reason);
+ xml.optElement(TEXT_ELEMENT, text);
+ xml.optAppend(element);
xml.closeElement(this);
return xml;
@@ -142,7 +173,11 @@ public class JingleReason implements XmlElement {
private final String sessionId;
public AlternativeSession(String sessionId) {
- super(Reason.alternative_session);
+ this(sessionId, null, null);
+ }
+
+ public AlternativeSession(String sessionId, String text, XmlElement element) {
+ super(Reason.alternative_session, text, element);
if (StringUtils.isNullOrEmpty(sessionId)) {
throw new NullPointerException("SessionID must not be null or empty.");
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java
index 8ff1e269b..40dfe9fc4 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2017-2019 Florian Schmaus
+ * Copyright 2017-2022 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,11 +19,14 @@ package org.jivesoftware.smackx.jingle.provider;
import java.io.IOException;
import java.util.logging.Logger;
+import org.jivesoftware.smack.packet.IqData;
import org.jivesoftware.smack.packet.StandardExtensionElement;
+import org.jivesoftware.smack.packet.XmlElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.parsing.StandardExtensionElementProvider;
-import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.provider.IqProvider;
+import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
@@ -40,13 +43,13 @@ import org.jivesoftware.smackx.jingle.element.UnknownJingleContentTransport;
import org.jxmpp.jid.FullJid;
-public class JingleProvider extends IQProvider {
+public class JingleProvider extends IqProvider {
private static final Logger LOGGER = Logger.getLogger(JingleProvider.class.getName());
@Override
- public Jingle parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
- Jingle.Builder builder = Jingle.getBuilder();
+ public Jingle parse(XmlPullParser parser, int initialDepth, IqData iqData, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
+ Jingle.Builder builder = Jingle.builder(iqData);
String actionString = parser.getAttributeValue("", Jingle.ACTION_ATTRIBUTE_NAME);
if (actionString != null) {
@@ -75,16 +78,7 @@ public class JingleProvider extends IQProvider {
builder.addJingleContent(content);
break;
case JingleReason.ELEMENT:
- parser.next();
- String reasonString = parser.getName();
- JingleReason reason;
- if (reasonString.equals("alternative-session")) {
- parser.next();
- String sid = parser.nextText();
- reason = new JingleReason.AlternativeSession(sid);
- } else {
- reason = new JingleReason(Reason.fromString(reasonString));
- }
+ JingleReason reason = parseJingleReason(parser);
builder.setReason(reason);
break;
default:
@@ -177,4 +171,57 @@ public class JingleProvider extends IQProvider {
return builder.build();
}
+
+ public static JingleReason parseJingleReason(XmlPullParser parser)
+ throws XmlPullParserException, IOException, SmackParsingException {
+ ParserUtils.assertAtStartTag(parser);
+ final int initialDepth = parser.getDepth();
+ final String jingleNamespace = parser.getNamespace();
+
+ JingleReason.Reason reason = null;
+ XmlElement element = null;
+ String text = null;
+
+ // 'sid' is only set if the reason is 'alternative-session'.
+ String sid = null;
+
+ outerloop: while (true) {
+ XmlPullParser.TagEvent event = parser.nextTag();
+ switch (event) {
+ case START_ELEMENT:
+ String elementName = parser.getName();
+ String namespace = parser.getNamespace();
+ if (namespace.equals(jingleNamespace)) {
+ switch (elementName) {
+ case "text":
+ text = parser.nextText();
+ break;
+ case "alternative-session":
+ parser.next();
+ sid = parser.nextText();
+ break;
+ default:
+ reason = Reason.fromString(elementName);
+ break;
+ }
+ } else {
+ element = PacketParserUtils.parseExtensionElement(elementName, namespace, parser, null);
+ }
+ break;
+ case END_ELEMENT:
+ if (parser.getDepth() == initialDepth) {
+ break outerloop;
+ }
+ break;
+ }
+ }
+
+ JingleReason res;
+ if (sid != null) {
+ res = new JingleReason.AlternativeSession(sid, text, element);
+ } else {
+ res = new JingleReason(reason, text, element);
+ }
+ return res;
+ }
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java
index 5b348c5d3..8c91c8976 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java
@@ -36,6 +36,10 @@ public abstract class JingleTransportManager i
}
public XMPPConnection getConnection() {
+ return connection();
+ }
+
+ public XMPPConnection connection() {
return connection;
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java
index 06accc144..8211820fe 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java
@@ -148,7 +148,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager {
+ private static final DoOnce LOG_OBJECT_NOT_ENABLED = new DoOnce();
+
private static final Logger LOGGER = Logger.getLogger(JivePropertiesExtensionProvider.class.getName());
/**
@@ -113,7 +116,10 @@ public class JivePropertiesExtensionProvider extends ExtensionElementProvider LOGGER.severe(
+ "JavaObject is not enabled. Enable with JivePropertiesManager.setJavaObjectEnabled(true)")
+ );
}
}
if (name != null && value != null) {
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java
index 8d941ee06..1735d0b4f 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java
@@ -78,6 +78,17 @@ public class MucConfigFormManager {
*/
public static final String MUC_ROOMCONFIG_ROOMSECRET = "muc#roomconfig_roomsecret";
+ /**
+ * The constant String {@value}.
+ */
+ public static final String MUC_ROOMCONFIG_MODERATEDROOM = "muc#roomconfig_moderatedroom";
+
+ /**
+ * The constant String {@value}.
+ */
+ public static final String MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM = "muc#roomconfig_publicroom";
+
+
private final MultiUserChat multiUserChat;
private final FillableForm answerForm;
private final List owners;
@@ -151,6 +162,15 @@ public class MucConfigFormManager {
return answerForm.hasField(MUC_ROOMCONFIG_MEMBERSONLY);
}
+ /**
+ * Check if the room supports being moderated in the configuration.
+ *
+ * @return true
if supported, false
if not.
+ */
+ public boolean supportsModeration() {
+ return answerForm.hasField(MUC_ROOMCONFIG_MODERATEDROOM);
+ }
+
/**
* Make the room for members only.
*
@@ -176,6 +196,68 @@ public class MucConfigFormManager {
return this;
}
+
+ /**
+ * Make the room moderated.
+ *
+ * @return a reference to this object.
+ * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
+ */
+ public MucConfigFormManager makeModerated() throws MucConfigurationNotSupportedException {
+ return setModerated(true);
+ }
+
+ /**
+ * Set if the room is members only. Rooms are not members only per default.
+ *
+ * @param isModerated if the room should be moderated.
+ * @return a reference to this object.
+ * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
+ */
+ public MucConfigFormManager setModerated(boolean isModerated) throws MucConfigurationNotSupportedException {
+ if (!supportsModeration()) {
+ throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_MODERATEDROOM);
+ }
+ answerForm.setAnswer(MUC_ROOMCONFIG_MODERATEDROOM, isModerated);
+ return this;
+ }
+
+
+ /**
+ * Make the room publicly searchable.
+ *
+ * @return a reference to this object.
+ * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
+ */
+ public MucConfigFormManager makePublic() throws MucConfigurationNotSupportedException {
+ return setPublic(true);
+ }
+
+ /**
+ * Make the room hidden (not publicly searchable).
+ *
+ * @return a reference to this object.
+ * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
+ */
+ public MucConfigFormManager makeHidden() throws MucConfigurationNotSupportedException {
+ return setPublic(false);
+ }
+
+ /**
+ * Set if the room is publicly searchable (i.e. visible via discovery requests to the MUC service).
+ *
+ * @param isPublic if the room should be publicly searchable.
+ * @return a reference to this object.
+ * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
+ */
+ public MucConfigFormManager setPublic(boolean isPublic) throws MucConfigurationNotSupportedException {
+ if (!supportsModeration()) {
+ throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM);
+ }
+ answerForm.setAnswer(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM, isPublic);
+ return this;
+ }
+
/**
* Check if the room supports password protection.
*
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 dba422d43..1064ee59d 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
@@ -205,12 +205,20 @@ public class MultiUserChat {
if (from == null) {
return;
}
- final EntityFullJid myRoomJID = myRoomJid;
+ final EntityFullJid myRoomJID = getMyRoomJid();
final boolean isUserStatusModification = presence.getFrom().equals(myRoomJID);
final MUCUser mucUser = MUCUser.from(packet);
switch (presence.getType()) {
case available:
+ if (!processedReflectedSelfPresence
+ && mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110)) {
+ processedReflectedSelfPresence = true;
+ synchronized (this) {
+ notify();
+ }
+ }
+
Presence oldPresence = occupantsMap.put(from, presence);
if (oldPresence != null) {
// Get the previous occupant's affiliation & role
@@ -228,11 +236,6 @@ public class MultiUserChat {
newAffiliation,
isUserStatusModification,
from);
- } else if (mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110)) {
- processedReflectedSelfPresence = true;
- synchronized (this) {
- notify();
- }
} else {
// A new occupant has joined the room
for (ParticipantStatusListener listener : participantStatusListeners) {
@@ -259,23 +262,24 @@ public class MultiUserChat {
listener.left(from);
}
}
+ }
- Destroy destroy = mucUser.getDestroy();
- // The room has been destroyed.
- if (destroy != null) {
- EntityBareJid alternateMucJid = destroy.getJid();
- final MultiUserChat alternateMuc;
- if (alternateMucJid == null) {
- alternateMuc = null;
- } else {
- alternateMuc = multiUserChatManager.getMultiUserChat(alternateMucJid);
- }
+ Destroy destroy = mucUser.getDestroy();
+ // The room has been destroyed.
+ if (destroy != null) {
+ EntityBareJid alternateMucJid = destroy.getJid();
+ final MultiUserChat alternateMuc;
+ if (alternateMucJid == null) {
+ alternateMuc = null;
+ } else {
+ alternateMuc = multiUserChatManager.getMultiUserChat(alternateMucJid);
+ }
- for (UserStatusListener listener : userStatusListeners) {
- listener.roomDestroyed(alternateMuc, destroy.getReason());
- }
+ for (UserStatusListener listener : userStatusListeners) {
+ listener.roomDestroyed(alternateMuc, destroy.getReason());
}
}
+
if (isUserStatusModification) {
for (UserStatusListener listener : userStatusListeners) {
listener.removed(mucUser, presence);
@@ -728,7 +732,7 @@ public class MultiUserChat {
* @return true if currently in the multi user chat room.
*/
public boolean isJoined() {
- return myRoomJid != null;
+ return getMyRoomJid() != null;
}
/**
@@ -764,7 +768,7 @@ public class MultiUserChat {
// "if (!joined) return" because it should be always be possible to leave the room in case the instance's
// state does not reflect the actual state.
- final EntityFullJid myRoomJid = this.myRoomJid;
+ final EntityFullJid myRoomJid = getMyRoomJid();
if (myRoomJid == null) {
throw new MucNotJoinedException(this);
}
@@ -793,11 +797,14 @@ public class MultiUserChat {
StanzaFilter reflectedLeavePresenceFilter = new AndFilter(reflectedLeavePresenceFilters);
- // Reset occupant information first so that we are assume that we left the room even if sendStanza() would
- // throw.
- userHasLeft();
-
- Presence reflectedLeavePresence = connection.createStanzaCollectorAndSend(reflectedLeavePresenceFilter, leavePresence).nextResultOrThrow();
+ Presence reflectedLeavePresence;
+ try {
+ reflectedLeavePresence = connection.createStanzaCollectorAndSend(reflectedLeavePresenceFilter, leavePresence).nextResultOrThrow();
+ } finally {
+ // Reset occupant information after we send the leave presence. This ensures that we only call userHasLeft()
+ // and reset the local MUC state after we successfully left the MUC (or if an exception occurred).
+ userHasLeft();
+ }
return reflectedLeavePresence;
}
@@ -1193,13 +1200,23 @@ public class MultiUserChat {
* @return the nickname currently being used.
*/
public Resourcepart getNickname() {
- final EntityFullJid myRoomJid = this.myRoomJid;
+ final EntityFullJid myRoomJid = getMyRoomJid();
if (myRoomJid == null) {
return null;
}
return myRoomJid.getResourcepart();
}
+ /**
+ * Return the full JID of the user in the room, or null
if the room is not joined.
+ *
+ * @return the full JID of the user in the room, or null
.
+ * @since 4.5.0
+ */
+ public EntityFullJid getMyRoomJid() {
+ return myRoomJid;
+ }
+
/**
* Changes the occupant's nickname to a new nickname within the room. Each room occupant
* will receive two presence packets. One of type "unavailable" for the old nickname and one
@@ -1256,7 +1273,7 @@ public class MultiUserChat {
* @throws MucNotJoinedException if not joined to the Multi-User Chat.
*/
public void changeAvailabilityStatus(String status, Presence.Mode mode) throws NotConnectedException, InterruptedException, MucNotJoinedException {
- final EntityFullJid myRoomJid = this.myRoomJid;
+ final EntityFullJid myRoomJid = getMyRoomJid();
if (myRoomJid == null) {
throw new MucNotJoinedException(this);
}
@@ -2579,7 +2596,7 @@ public class MultiUserChat {
}
public boolean serviceSupportsStableIds() {
- return mucServiceDiscoInfo.containsFeature(MultiUserChatConstants.STABLE_ID_FEATURE);
+ return DiscoverInfo.nullSafeContainsFeature(mucServiceDiscoInfo, MultiUserChatConstants.STABLE_ID_FEATURE);
}
@Override
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java
index 3a6945f0f..ef8f3373c 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java
@@ -38,11 +38,7 @@ public class FormNode extends NodeExtension {
* @param submitForm The form
*/
public FormNode(FormNodeType formType, DataForm submitForm) {
- super(formType.getNodeElement());
-
- if (submitForm == null)
- throw new IllegalArgumentException("Submit form cannot be null");
- configForm = submitForm;
+ this(formType, null, submitForm);
}
/**
@@ -55,9 +51,6 @@ public class FormNode extends NodeExtension {
*/
public FormNode(FormNodeType formType, String nodeId, DataForm submitForm) {
super(formType.getNodeElement(), nodeId);
-
- if (submitForm == null)
- throw new IllegalArgumentException("Submit form cannot be null");
configForm = submitForm;
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java
index e7d8b0946..5c5d71dc7 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java
@@ -35,6 +35,11 @@ import org.jivesoftware.smackx.xdata.packet.DataForm;
public class FormNodeProvider extends EmbeddedExtensionProvider {
@Override
protected FormNode createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List extends XmlElement> content) {
- return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), (DataForm) content.iterator().next());
+ DataForm dataForm = null;
+ if (!content.isEmpty()) {
+ dataForm = (DataForm) content.get(0);
+ }
+
+ return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), dataForm);
}
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java
index 4c7d0e7ce..5842fda91 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java
@@ -50,24 +50,25 @@ public class ItemProvider extends ExtensionElementProvider- {
String xmlns = parser.getNamespace();
ItemNamespace itemNamespace = ItemNamespace.fromXmlns(xmlns);
- XmlPullParser.Event tag = parser.next();
+ XmlPullParser.TagEvent event = parser.nextTag();
+ switch (event) {
+ case START_ELEMENT:
+ String payloadElemName = parser.getName();
+ String payloadNS = parser.getNamespace();
- if (tag == XmlPullParser.Event.END_ELEMENT) {
- return new Item(itemNamespace, id, node);
- }
- else {
- String payloadElemName = parser.getName();
- String payloadNS = parser.getNamespace();
-
- final ExtensionElementProvider extensionProvider = ProviderManager.getExtensionProvider(payloadElemName, payloadNS);
- if (extensionProvider == null) {
- // TODO: Should we use StandardExtensionElement in this case? And probably remove SimplePayload all together.
- CharSequence payloadText = PacketParserUtils.parseElement(parser, true);
- return new PayloadItem<>(itemNamespace, id, node, new SimplePayload(payloadText.toString()));
- }
- else {
- return new PayloadItem<>(itemNamespace, id, node, extensionProvider.parse(parser));
- }
+ final ExtensionElementProvider extensionProvider = ProviderManager.getExtensionProvider(payloadElemName, payloadNS);
+ if (extensionProvider == null) {
+ // TODO: Should we use StandardExtensionElement in this case? And probably remove SimplePayload all together.
+ CharSequence payloadText = PacketParserUtils.parseElement(parser, true);
+ return new PayloadItem<>(itemNamespace, id, node, new SimplePayload(payloadText.toString()));
+ }
+ else {
+ return new PayloadItem<>(itemNamespace, id, node, extensionProvider.parse(parser));
+ }
+ case END_ELEMENT:
+ return new Item(itemNamespace, id, node);
+ default:
+ throw new AssertionError("unknown: " + event);
}
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/softwareinfo/form/SoftwareInfoForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/softwareinfo/form/SoftwareInfoForm.java
index 1d6a7af4a..f2327face 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/softwareinfo/form/SoftwareInfoForm.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/softwareinfo/form/SoftwareInfoForm.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Aditya Borikar
+ * Copyright 2020 Aditya Borikar, 2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@ import java.util.List;
import org.jivesoftware.smack.util.EqualsUtil;
import org.jivesoftware.smack.util.HashCode;
-
+import org.jivesoftware.smackx.formtypes.FormFieldRegistry;
import org.jivesoftware.smackx.mediaelement.element.MediaElement;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.FormFieldChildElement;
@@ -47,6 +47,11 @@ public final class SoftwareInfoForm extends FilledForm {
public static final String SOFTWARE_VERSION = "software_version";
public static final String ICON = "icon";
+ static {
+ FormFieldRegistry.register(FORM_TYPE, FormField.Type.text_single,
+ OS, OS_VERSION, SOFTWARE, SOFTWARE_VERSION);
+ }
+
private SoftwareInfoForm(DataForm dataForm) {
super(dataForm);
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java
index 4b1c092b3..16ccc3c7f 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java
@@ -27,35 +27,26 @@ import org.jxmpp.util.XmppDateTime;
public class AbstractMultiFormField extends FormField {
- private final List values;
-
- private final List rawValues;
+ private final List values;
protected AbstractMultiFormField(Builder, ?> builder) {
super(builder);
values = CollectionUtil.cloneAndSeal(builder.values);
- rawValues = CollectionUtil.cloneAndSeal(builder.rawValues);
}
@Override
- public final List getValues() {
+ public final List getRawValues() {
return values;
}
- @Override
- public final List getRawValues() {
- return rawValues;
- }
-
public abstract static class Builder>
extends FormField.Builder {
- private List values;
- private List rawValues;
+ private List values;
protected Builder(AbstractMultiFormField formField) {
super(formField);
- values = CollectionUtil.newListWith(formField.getValues());
+ values = CollectionUtil.newListWith(formField.getRawValues());
}
protected Builder(String fieldName, FormField.Type type) {
@@ -65,7 +56,6 @@ public class AbstractMultiFormField extends FormField {
private void ensureValuesAreInitialized() {
if (values == null) {
values = new ArrayList<>();
- rawValues = new ArrayList<>();
}
}
@@ -77,11 +67,13 @@ public class AbstractMultiFormField extends FormField {
public abstract B addValue(CharSequence value);
public B addValueVerbatim(CharSequence value) {
+ return addValueVerbatim(new Value(value));
+ }
+
+ public B addValueVerbatim(Value value) {
ensureValuesAreInitialized();
- String valueString = value.toString();
- values.add(valueString);
- rawValues.add(valueString);
+ values.add(value);
return getThis();
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java
index 4530d3968..9e57a294e 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java
@@ -74,8 +74,15 @@ public class AbstractSingleStringValueFormField extends SingleValueFormField {
return setValue(value);
}
+ public B setValue(Value value) {
+ this.value = value.getValue().toString();
+ this.rawValue = value;
+ return getThis();
+ }
+
public B setValue(CharSequence value) {
- this.rawValue = this.value = value.toString();
+ this.value = value.toString();
+ rawValue = new Value(this.value);
return getThis();
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java
index e87278ae9..6681e6408 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java
@@ -35,7 +35,31 @@ public class BooleanFormField extends SingleValueFormField {
return value.toString();
}
- public Boolean getValueAsBoolean() {
+ /**
+ * Get the value of the booelan field. Note that, if no explicit boolean value is provided, in the form of "true",
+ * "false", "0", or "1", then the default value of a boolean field is
false
, according to
+ * XEP-0004 § 3.3.
+ *
+ * @return the boolean value of this form field.
+ */
+ public boolean getValueAsBoolean() {
+ if (value == null) {
+ return false;
+ }
+ return value;
+ }
+
+ /**
+ * Get the value of the boolean field or maybe null
. Note that you usually want to use
+ * {@link #getValueAsBoolean()} instead of this method, as {@link #getValueAsBoolean()} considers the default value
+ * of boolean fields. That is, boolean form fields have the value false
if not explicitly set to
+ * something else.
+ *
+ * @return the boolean value of this form field or null
if no value was explicitly provided.
+ * @see #getValueAsBoolean()
+ * @since 4.4.5
+ */
+ public Boolean getValueAsBooleanOrNull() {
return value;
}
@@ -71,17 +95,21 @@ public class BooleanFormField extends SingleValueFormField {
@Deprecated
// TODO: Remove in Smack 4.6.
public Builder addValue(CharSequence value) {
- return setValue(value);
+ return setValue(new Value(value));
}
public Builder setValue(CharSequence value) {
- rawValue = value.toString();
- boolean valueBoolean = ParserUtils.parseXmlBoolean(rawValue);
- return setValue(valueBoolean);
+ return setValue(new Value(value));
+ }
+
+ public Builder setValue(Value value) {
+ this.value = ParserUtils.parseXmlBoolean(value.getValue().toString());
+ rawValue = value;
+ return getThis();
}
public Builder setValue(boolean value) {
- rawValue = Boolean.toString(value);
+ rawValue = new Value(Boolean.toString(value));
this.value = value;
return this;
}
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 8f4e9aafd..a2ad8cb76 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
@@ -33,6 +33,7 @@ import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.EqualsUtil;
import org.jivesoftware.smack.util.HashCode;
import org.jivesoftware.smack.util.MultiMap;
+import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder;
@@ -269,9 +270,25 @@ public abstract class FormField implements XmlElement {
*
* @return a List of the default values or answered values of the question.
*/
- public abstract List extends CharSequence> getValues();
+ public List extends CharSequence> getValues() {
+ return getRawValueCharSequences();
+ }
- public abstract List getRawValues();
+ public abstract List getRawValues();
+
+ private transient List rawValueCharSequences;
+
+ public final List getRawValueCharSequences() {
+ if (rawValueCharSequences == null) {
+ List rawValues = getRawValues();
+ rawValueCharSequences = new ArrayList<>(rawValues.size());
+ for (Value value : rawValues) {
+ rawValueCharSequences.add(value.value);
+ }
+ }
+
+ return rawValueCharSequences;
+ }
public boolean hasValueSet() {
List> values = getValues();
@@ -385,12 +402,15 @@ public abstract class FormField implements XmlElement {
protected transient List extraXmlChildElements;
+ /**
+ * Populate @{link {@link #extraXmlChildElements}}. Note that this method may be overridden by subclasses.
+ */
protected void populateExtraXmlChildElements() {
- List extends CharSequence> values = getValues();
+ List values = getRawValues();
+ // Note that we need to create a new ArrayList here, since subclasses may add to it by overriding
+ // populateExtraXmlChildElements.
extraXmlChildElements = new ArrayList<>(values.size());
- for (CharSequence value : values) {
- extraXmlChildElements.add(new Value(value));
- }
+ extraXmlChildElements.addAll(values);
}
@Override
@@ -414,7 +434,8 @@ public abstract class FormField implements XmlElement {
populateExtraXmlChildElements();
}
- if (formFieldChildElements.isEmpty() && extraXmlChildElements == null) {
+ if (formFieldChildElements.isEmpty()
+ && (extraXmlChildElements == null || extraXmlChildElements.isEmpty())) {
buf.closeEmptyElement();
} else {
buf.rightAngleBracket();
@@ -580,7 +601,7 @@ public abstract class FormField implements XmlElement {
* @return a reference to this builder.
*/
public B setLabel(String label) {
- this.label = StringUtils.requireNotNullNorEmpty(label, "label must not be null or empty");
+ this.label = Objects.requireNonNull(label, "label must not be null");
return getThis();
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidMultiFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidMultiFormField.java
index a5c869c18..39d8357af 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidMultiFormField.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidMultiFormField.java
@@ -23,13 +23,14 @@ import java.util.List;
import org.jivesoftware.smack.util.CollectionUtil;
import org.jxmpp.jid.Jid;
-import org.jxmpp.jid.util.JidUtil;
+import org.jxmpp.jid.impl.JidCreate;
+import org.jxmpp.stringprep.XmppStringprepException;
public final class JidMultiFormField extends FormField {
private final List values;
- private final List rawValues;
+ private final List rawValues;
JidMultiFormField(Builder builder) {
super(builder);
@@ -43,7 +44,7 @@ public final class JidMultiFormField extends FormField {
}
@Override
- public List getRawValues() {
+ public List getRawValues() {
return rawValues;
}
@@ -54,7 +55,7 @@ public final class JidMultiFormField extends FormField {
public static final class Builder extends FormField.Builder {
private List values;
- private List rawValues;
+ private List rawValues;
private Builder(JidMultiFormField jidMultiFormField) {
super(jidMultiFormField);
@@ -79,27 +80,29 @@ public final class JidMultiFormField extends FormField {
}
public Builder addValue(Jid jid) {
- return addValue(jid, null);
- }
-
- public Builder addValue(Jid jid, String rawValue) {
- if (rawValue == null) {
- rawValue = jid.toString();
- }
+ Value value = new Value(jid);
ensureValuesAreInitialized();
-
values.add(jid);
- rawValues.add(rawValue);
+ rawValues.add(value);
+
+ return getThis();
+ }
+
+ public Builder addValue(Value value) throws XmppStringprepException {
+ Jid jid = JidCreate.from(value.getValue());
+
+ ensureValuesAreInitialized();
+ values.add(jid);
+ rawValues.add(value);
return this;
}
public Builder addValues(Collection extends Jid> jids) {
- ensureValuesAreInitialized();
-
- values.addAll(jids);
- rawValues.addAll(JidUtil.toStringList(jids));
+ for (Jid jid : jids) {
+ addValue(jid);
+ }
return this;
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidSingleFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidSingleFormField.java
index 7948971a3..ec5ec4c70 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidSingleFormField.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidSingleFormField.java
@@ -17,6 +17,8 @@
package org.jivesoftware.smackx.xdata;
import org.jxmpp.jid.Jid;
+import org.jxmpp.jid.impl.JidCreate;
+import org.jxmpp.stringprep.XmppStringprepException;
public class JidSingleFormField extends SingleValueFormField {
@@ -60,11 +62,13 @@ public class JidSingleFormField extends SingleValueFormField {
public Builder setValue(Jid value, String rawValue) {
this.value = value;
- if (rawValue != null) {
- this.rawValue = rawValue;
- } else {
- this.rawValue = value.toString();
- }
+ this.rawValue = new Value(value);
+ return getThis();
+ }
+
+ public Builder setValue(Value value) throws XmppStringprepException {
+ this.value = JidCreate.from(value.getValue());
+ this.rawValue = value;
return this;
}
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java
index 796abd654..d4b389a96 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java
@@ -23,7 +23,7 @@ import org.jivesoftware.smack.util.CollectionUtil;
public abstract class SingleValueFormField extends FormField {
- private final String rawValue;
+ private final Value rawValue;
protected SingleValueFormField(Builder, ?> builder) {
super(builder);
@@ -38,24 +38,23 @@ public abstract class SingleValueFormField extends FormField {
public abstract CharSequence getValue();
- public final String getRawValue() {
+ public final Value getRawValue() {
return rawValue;
}
@Override
- public final List getRawValues() {
- String rawValue = getRawValue();
+ public final List getRawValues() {
+ Value rawValue = getRawValue();
return CollectionUtil.emptyOrSingletonListFrom(rawValue);
}
@Override
protected void populateExtraXmlChildElements() {
- CharSequence value = getValue();
- if (value == null) {
+ if (rawValue == null) {
return;
}
- extraXmlChildElements = Collections.singletonList(new Value(value));
+ extraXmlChildElements = Collections.singletonList(rawValue);
}
public abstract static class Builder>
@@ -65,11 +64,12 @@ public abstract class SingleValueFormField extends FormField {
super(fieldName, type);
}
- protected Builder(FormField formField) {
+ protected Builder(SingleValueFormField formField) {
super(formField);
+ rawValue = formField.getRawValue();
}
- protected String rawValue;
+ protected Value rawValue;
@Override
protected void resetInternal() {
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java
index 493abd25a..043e9a1fe 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2020 Florian Schmaus
+ * Copyright 2020-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
+import org.jivesoftware.smack.util.StringUtils;
+
import org.jivesoftware.smackx.xdata.AbstractMultiFormField;
import org.jivesoftware.smackx.xdata.AbstractSingleStringValueFormField;
import org.jivesoftware.smackx.xdata.BooleanFormField;
@@ -54,7 +56,8 @@ public interface FormReader {
return Collections.emptyList();
}
AbstractMultiFormField multiFormField = formField.ifPossibleAs(AbstractMultiFormField.class);
- return multiFormField.getValues();
+ List extends CharSequence> charSequences = multiFormField.getValues();
+ return StringUtils.toStrings(charSequences);
}
default Boolean readBoolean(String fieldName) {
diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java
index f6362660c..0be42d61f 100644
--- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java
+++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java
@@ -49,9 +49,6 @@ import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jivesoftware.smackx.xdatalayout.packet.DataLayout;
import org.jivesoftware.smackx.xdatalayout.provider.DataLayoutProvider;
-import org.jxmpp.jid.Jid;
-import org.jxmpp.jid.impl.JidCreate;
-
/**
* The DataFormProvider parses DataForm packets.
*
@@ -237,9 +234,7 @@ public class DataFormProvider extends ExtensionElementProvider {
case jid_multi:
JidMultiFormField.Builder jidMultiBuilder = FormField.jidMultiBuilder(fieldName);
for (FormField.Value value : values) {
- String rawValue = value.getValue().toString();
- Jid jid = JidCreate.from(rawValue);
- jidMultiBuilder.addValue(jid, rawValue);
+ jidMultiBuilder.addValue(value);
}
builder = jidMultiBuilder;
break;
@@ -247,9 +242,8 @@ public class DataFormProvider extends ExtensionElementProvider {
ensureAtMostSingleValue(type, values);
JidSingleFormField.Builder jidSingleBuilder = FormField.jidSingleBuilder(fieldName);
if (!values.isEmpty()) {
- String rawValue = values.get(0).getValue().toString();
- Jid jid = JidCreate.from(rawValue);
- jidSingleBuilder.setValue(jid, rawValue);
+ FormField.Value value = values.get(0);
+ jidSingleBuilder.setValue(value);
}
builder = jidSingleBuilder;
break;
@@ -303,7 +297,7 @@ public class DataFormProvider extends ExtensionElementProvider {
BooleanFormField.Builder builder = FormField.booleanBuilder(fieldName);
ensureAtMostSingleValue(builder.getType(), values);
if (values.size() == 1) {
- String value = values.get(0).getValue().toString();
+ FormField.Value value = values.get(0);
builder.setValue(value);
}
return builder;
diff --git a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml
index d234bb5b3..5921821a1 100644
--- a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml
+++ b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml
@@ -20,5 +20,6 @@
org.jivesoftware.smackx.receipts.DeliveryReceiptManager
org.jivesoftware.smackx.iqversion.VersionManager
org.jivesoftware.smackx.caps.EntityCapsManager
+ org.jivesoftware.smackx.softwareinfo.form.SoftwareInfoForm
diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java
index af8426029..f8df83f83 100644
--- a/smack-extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java
+++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java
@@ -67,7 +67,6 @@ public class AMPExtensionTest {
AMPExtensionProvider ampProvider = new AMPExtensionProvider();
XmlPullParser parser = PacketParserUtils.getParserFor(INCORRECT_RECEIVING_STANZA_STREAM);
- assertEquals(XmlPullParser.Event.START_ELEMENT, parser.next());
assertEquals(AMPExtension.ELEMENT, parser.getName());
ExtensionElement extension = ampProvider.parse(parser);
@@ -85,7 +84,6 @@ public class AMPExtensionTest {
AMPExtensionProvider ampProvider = new AMPExtensionProvider();
XmlPullParser parser = PacketParserUtils.getParserFor(CORRECT_SENDING_STANZA_STREAM);
- assertEquals(XmlPullParser.Event.START_ELEMENT, parser.next());
assertEquals(AMPExtension.ELEMENT, parser.getName());
ExtensionElement extension = ampProvider.parse(parser);
assertTrue(extension instanceof AMPExtension);
diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java
index 45904af4e..946ae95e8 100644
--- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java
+++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java
@@ -75,8 +75,7 @@ public class JingleContentTest extends SmackTestSuite {
assertEquals(content1.toXML().toString(), builder.build().toXML().toString());
String xml =
- "" +
- "";
+ "";
assertEquals(xml, content1.toXML().toString());
}
}
diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleTest.java
index c8ac6c926..2f8147947 100644
--- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleTest.java
+++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleTest.java
@@ -38,7 +38,7 @@ public class JingleTest extends SmackTestSuite {
@Test
public void emptyBuilderTest() {
- Jingle.Builder builder = Jingle.getBuilder();
+ Jingle.Builder builder = Jingle.builder("id");
assertThrows(IllegalArgumentException.class, () -> {
builder.build();
});
@@ -48,7 +48,7 @@ public class JingleTest extends SmackTestSuite {
public void onlySessionIdBuilderTest() {
String sessionId = "testSessionId";
- Jingle.Builder builder = Jingle.getBuilder();
+ Jingle.Builder builder = Jingle.builder("id");
builder.setSessionId(sessionId);
assertThrows(IllegalArgumentException.class, () -> {
builder.build();
@@ -59,7 +59,7 @@ public class JingleTest extends SmackTestSuite {
public void parserTest() throws XmppStringprepException {
String sessionId = "testSessionId";
- Jingle.Builder builder = Jingle.getBuilder();
+ Jingle.Builder builder = Jingle.builder("id");
builder.setSessionId(sessionId);
builder.setAction(JingleAction.session_initiate);
diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/element/JingleTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/element/JingleTest.java
new file mode 100644
index 000000000..ae198617f
--- /dev/null
+++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/element/JingleTest.java
@@ -0,0 +1,48 @@
+/**
+ *
+ * Copyright 2021 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.jingle.element;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.jivesoftware.smack.packet.StreamOpen;
+
+import org.junit.jupiter.api.Test;
+
+public class JingleTest {
+
+ @Test
+ public void noRedundantNamespaceTest() {
+ Jingle.Builder jingleBuilder = Jingle.builder("test-id");
+ jingleBuilder.setSessionId("MySession");
+ jingleBuilder.setAction(JingleAction.content_accept);
+
+ JingleContent.Builder jingleContentBuilder = JingleContent.getBuilder();
+ jingleContentBuilder.setName("Hello world");
+ jingleContentBuilder.setCreator(JingleContent.Creator.initiator);
+
+ jingleBuilder.addJingleContent(jingleContentBuilder.build());
+ Jingle iq = jingleBuilder.build();
+
+ String actualXml = iq.toXML(StreamOpen.CLIENT_NAMESPACE).toString();
+ String expectedXml
+ = ""
+ + ""
+ + ""
+ + "";
+ assertEquals(expectedXml, actualXml);
+ }
+}
diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java
index c17543148..9385a88f8 100644
--- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java
+++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2017 Florian Schmaus
+ * Copyright 2017-2022 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.jingle.provider;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
-import org.jivesoftware.smack.util.PacketParserUtils;
-import org.jivesoftware.smack.xml.XmlPullParser;
+import org.jivesoftware.smack.packet.StandardExtensionElement;
+import org.jivesoftware.smack.packet.XmlElement;
+import org.jivesoftware.smack.parsing.SmackParsingException;
+import org.jivesoftware.smack.test.util.SmackTestUtil;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.jingle.element.Jingle;
import org.jivesoftware.smackx.jingle.element.JingleContentDescription;
import org.jivesoftware.smackx.jingle.element.JingleContentTransport;
+import org.jivesoftware.smackx.jingle.element.JingleReason;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
public class JingleProviderTest {
- @Test
- public void testParseUnknownJingleContentDescrption() throws Exception {
+ @ParameterizedTest
+ @EnumSource(SmackTestUtil.XmlPullParserKind.class)
+ public void testParseUnknownJingleContentDescrption(SmackTestUtil.XmlPullParserKind parserKind)
+ throws XmlPullParserException, IOException, SmackParsingException {
final String unknownJingleContentDescriptionNamespace = "urn:xmpp:jingle:unknown-description:5";
final String unknownJingleContentDescription =
// @formatter:off
@@ -50,8 +57,8 @@ public class JingleProviderTest {
"" +
"";
// @formatter:on
- XmlPullParser parser = createTestJingle(unknownJingleContentDescription);
- Jingle jingle = (Jingle) PacketParserUtils.parseIQ(parser);
+ CharSequence xml = createTestJingle(unknownJingleContentDescription);
+ Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind);
JingleContentDescription jingleContentDescription = jingle.getSoleContentOrThrow().getDescription();
@@ -59,8 +66,10 @@ public class JingleProviderTest {
assertEquals(unknownJingleContentDescriptionNamespace, parsedUnknownJingleContentDescriptionNamespace);
}
- @Test
- public void testParseUnknownJingleContentTransport() throws Exception {
+ @ParameterizedTest
+ @EnumSource(SmackTestUtil.XmlPullParserKind.class)
+ public void testParseUnknownJingleContentTransport(SmackTestUtil.XmlPullParserKind parserKind)
+ throws XmlPullParserException, IOException, SmackParsingException {
final String unknownJingleContentTransportNamespace = "urn:xmpp:jingle:unknown-transport:foo:1";
final String unknownJingleContentTransport =
// @formatter:off
@@ -81,8 +90,8 @@ public class JingleProviderTest {
" type='direct'/>" +
"";
// @formatter:on
- XmlPullParser parser = createTestJingle(unknownJingleContentTransport);
- Jingle jingle = (Jingle) PacketParserUtils.parseIQ(parser);
+ CharSequence xml = createTestJingle(unknownJingleContentTransport);
+ Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind);
JingleContentTransport jingleContentTransport = jingle.getSoleContentOrThrow().getTransport();
@@ -90,7 +99,38 @@ public class JingleProviderTest {
assertEquals(unknownJingleContentTransportNamespace, parsedUnknownJingleContentTransportNamespace);
}
- private static XmlPullParser createTestJingle(String... childs) throws XmlPullParserException, IOException {
+ @ParameterizedTest
+ @EnumSource(SmackTestUtil.XmlPullParserKind.class)
+ public void testReasonElementWithExtraElement(SmackTestUtil.XmlPullParserKind parserKind)
+ throws XmlPullParserException, IOException, SmackParsingException {
+ String xml = ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + "";
+ Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind);
+ JingleReason jingleReason = jingle.getReason();
+
+ assertEquals(JingleReason.Reason.success, jingleReason.asEnum());
+
+ XmlElement element = jingleReason.getElement();
+ // TODO: Use JUnit 5.8's assertInstanceOf when possible
+ // assertInstanceOf(StandardExtesionElement.class, extraElement);
+ assertTrue(element instanceof StandardExtensionElement);
+ StandardExtensionElement extraElement = (StandardExtensionElement) element;
+ assertEquals("https://example.org", extraElement.getNamespace());
+ assertEquals("bar", extraElement.getAttributes().get("foo"));
+ }
+
+ private static CharSequence createTestJingle(String... childs) throws XmlPullParserException, IOException {
StringBuilder sb = new StringBuilder();
sb.append(// @formatter:off
"" +
"" +
+ "action='session-initiate' " +
+ "initiator='romeo@montague.example/dr4hcr0st3lup4c' " +
+ "sid='851ba2'>" +
""
// @formatter:on
);
@@ -114,9 +154,6 @@ public class JingleProviderTest {
// @formatter:on
);
- String jingleStanza = sb.toString();
-
- XmlPullParser parser = PacketParserUtils.getParserFor(jingleStanza);
- return parser;
+ return sb;
}
}
diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/provider/ItemProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/provider/ItemProviderTest.java
new file mode 100644
index 000000000..e7348e920
--- /dev/null
+++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/provider/ItemProviderTest.java
@@ -0,0 +1,48 @@
+/**
+ *
+ * Copyright 2021 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.pubsub.provider;
+
+import java.io.IOException;
+
+import org.jivesoftware.smack.parsing.SmackParsingException;
+import org.jivesoftware.smack.test.util.SmackTestUtil;
+import org.jivesoftware.smack.xml.XmlPullParserException;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+public class ItemProviderTest {
+
+ /**
+ * Check that {@link ItemProvider} is able to parse items which have whitespace before their Payload.
+ *
+ * @param parserKind the used parser Kind
+ * @throws XmlPullParserException if an XML pull parser exception occurs.
+ * @throws IOException if an IO exception occurs.
+ * @throws SmackParsingException if an Smack parsing exception occurs.
+ * @see SMACK-918
+ */
+ @ParameterizedTest
+ @EnumSource(SmackTestUtil.XmlPullParserKind.class)
+ public void whitespaceBeforeItemPayload(SmackTestUtil.XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException {
+ String item = "- "
+ + "\n "
+ + "
";
+ SmackTestUtil.parse(item, ItemProvider.class, parserKind);
+ }
+
+}
diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java
index 67cd5dd89..d6d0232c9 100644
--- a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java
+++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java
@@ -17,6 +17,8 @@
package org.jivesoftware.smackx.xdata;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.JidTestUtil;
@@ -35,4 +37,18 @@ class FormFieldTest {
assertXmlSimilar(expectedXml, xml);
}
+ @Test
+ public void testEmptyLabel() {
+ TextSingleFormField.Builder builder = FormField.textSingleBuilder("type");
+ builder.setLabel("");
+ TextSingleFormField formField = builder.build();
+
+ assertEquals("", formField.getLabel());
+ }
+
+ @Test
+ public void testThrowExceptionWhenNullLabel() {
+ TextSingleFormField.Builder builder = FormField.textSingleBuilder("type");
+ assertThrows(IllegalArgumentException.class, () -> builder.setLabel(null));
+ }
}
diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/provider/DataFormProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/provider/DataFormProviderTest.java
index 90093023c..3d901a8fc 100644
--- a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/provider/DataFormProviderTest.java
+++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/provider/DataFormProviderTest.java
@@ -115,4 +115,33 @@ public class DataFormProviderTest {
assertEquals(2, items.size());
}
+ @Test
+ public void testRetrieveFieldWithEmptyLabel() throws XmlPullParserException, IOException, SmackParsingException {
+
+ String form =
+ "" +
+ " Advanced User Search" +
+ " The following fields are available for searching. Wildcard (*) characters are allowed as part of the query." +
+ " " +
+ " jabber:iq:search" +
+ " " +
+ " " +
+ " " +
+ " " +
+ " " +
+ " true" +
+ " " +
+ " " +
+ " true" +
+ " " +
+ " " +
+ " true" +
+ " " +
+ "";
+ XmlPullParser parser = PacketParserUtils.getParserFor(form);
+ DataForm dataForm = DataFormProvider.INSTANCE.parse(parser);
+ FormField usernameFormField = dataForm.getField("FORM_TYPE");
+ assertEquals(FormField.Type.hidden, usernameFormField.getType());
+ assertEquals("", usernameFormField.getLabel());
+ }
}
diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProviderTest.java
index 67557c553..5505e4dda 100644
--- a/smack-extensions/src/test/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProviderTest.java
+++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProviderTest.java
@@ -39,7 +39,6 @@ public class XHTMLExtensionProviderTest {
public void parsesWell() throws IOException, XmlPullParserException {
InputStream inputStream = getClass().getResourceAsStream(XHTML_EXTENSION_SAMPLE_RESOURCE_NAME);
XmlPullParser parser = PacketParserUtils.getParserFor(inputStream);
- parser.next();
XHTMLExtensionProvider provider = new XHTMLExtensionProvider();
ExtensionElement extension = provider.parse(parser, parser.getDepth(), null);
diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java
index 3d3198315..500894f07 100644
--- a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java
+++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java
@@ -469,11 +469,14 @@ public final class Roster extends Manager {
@Override
public void processException(Exception exception) {
rosterState = RosterState.uninitialized;
- Level logLevel;
+ Level logLevel = Level.SEVERE;
if (exception instanceof NotConnectedException) {
logLevel = Level.FINE;
- } else {
- logLevel = Level.SEVERE;
+ } else if (exception instanceof XMPPErrorException) {
+ Condition condition = ((XMPPErrorException) exception).getStanzaError().getCondition();
+ if (condition == Condition.feature_not_implemented || condition == Condition.service_unavailable) {
+ logLevel = Level.FINE;
+ }
}
LOGGER.log(logLevel, "Exception reloading roster", exception);
for (RosterLoadedListener listener : rosterLoadedListeners) {
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java
index 593d5b847..f3b083611 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2020 Florian Schmaus
+ * Copyright 2015-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -85,6 +85,7 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest
* @param action the action to perform.
* @throws Exception in case of an exception.
*/
+ @SuppressWarnings("ThreadPriorityCheck")
protected void performActionAndWaitForPresence(XMPPConnection conA, XMPPConnection conB, ThrowingRunnable action)
throws Exception {
final SimpleResultSyncPoint presenceReceivedSyncPoint = new SimpleResultSyncPoint();
@@ -109,5 +110,8 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest
} finally {
conA.removeAsyncStanzaListener(presenceListener);
}
+
+ // TODO: Ugly hack to make tests using this method more reliable. Ideally no test would use this method.
+ Thread.yield();
}
}
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java
index 8135e3deb..700c12eff 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2015-2020 Florian Schmaus
+ * Copyright 2015-2021 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,8 +19,11 @@ package org.igniterealtime.smack.inttest;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -33,6 +36,7 @@ import javax.net.ssl.SSLContext;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.debugger.ConsoleDebugger;
+import org.jivesoftware.smack.util.CollectionUtil;
import org.jivesoftware.smack.util.Function;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.ParserUtils;
@@ -101,8 +105,12 @@ public final class Configuration {
public final Set enabledTests;
+ private final Map> enabledTestsMap;
+
public final Set disabledTests;
+ private final Map> disabledTestsMap;
+
public final String defaultConnectionNickname;
public final Set enabledConnections;
@@ -117,6 +125,13 @@ public final class Configuration {
public final DnsResolver dnsResolver;
+ public enum CompatibilityMode {
+ standardsCompliant,
+ ejabberd,
+ }
+
+ public final CompatibilityMode compatibilityMode;
+
private Configuration(Configuration.Builder builder) throws KeyManagementException, NoSuchAlgorithmException {
service = Objects.requireNonNull(builder.service,
"'service' must be set. Either via 'properties' files or via system property 'sinttest.service'.");
@@ -162,8 +177,10 @@ public final class Configuration {
this.accountTwoPassword = builder.accountTwoPassword;
this.accountThreeUsername = builder.accountThreeUsername;
this.accountThreePassword = builder.accountThreePassword;
- this.enabledTests = builder.enabledTests;
- this.disabledTests = builder.disabledTests;
+ this.enabledTests = CollectionUtil.nullSafeUnmodifiableSet(builder.enabledTests);
+ this.enabledTestsMap = convertTestsToMap(enabledTests);
+ this.disabledTests = CollectionUtil.nullSafeUnmodifiableSet(builder.disabledTests);
+ this.disabledTestsMap = convertTestsToMap(disabledTests);
this.defaultConnectionNickname = builder.defaultConnectionNickname;
this.enabledConnections = builder.enabledConnections;
this.disabledConnections = builder.disabledConnections;
@@ -192,6 +209,7 @@ public final class Configuration {
this.verbose = builder.verbose;
this.dnsResolver = builder.dnsResolver;
+ this.compatibilityMode = builder.compatibilityMode;
}
public boolean isAccountRegistrationPossible() {
@@ -246,6 +264,8 @@ public final class Configuration {
private DnsResolver dnsResolver = DnsResolver.minidns;
+ private CompatibilityMode compatibilityMode = CompatibilityMode.standardsCompliant;
+
private Builder() {
}
@@ -427,6 +447,20 @@ public final class Configuration {
return setDnsResolver(dnsResolver);
}
+ public Builder setCompatibilityMode(CompatibilityMode compatibilityMode) {
+ this.compatibilityMode = compatibilityMode;
+ return this;
+ }
+
+ public Builder setCompatibilityMode(String compatibilityModeString) {
+ if (compatibilityModeString == null) {
+ return this;
+ }
+
+ CompatibilityMode compatibilityMode = CompatibilityMode.valueOf(compatibilityModeString);
+ return setCompatibilityMode(compatibilityMode);
+ }
+
public Configuration build() throws KeyManagementException, NoSuchAlgorithmException {
return new Configuration(this);
}
@@ -500,6 +534,8 @@ public final class Configuration {
builder.setDnsResolver(properties.getProperty("dnsResolver"));
+ builder.setCompatibilityMode(properties.getProperty("compatibilityMode"));
+
return builder.build();
}
@@ -551,4 +587,112 @@ public final class Configuration {
});
}
+ private static Map> convertTestsToMap(Set tests) {
+ Map> res = new HashMap<>();
+ for (String test : tests) {
+ String[] testParts = test.split("\\.");
+ if (testParts.length == 1) {
+ // The whole test specification does not contain a dot, assume it is a test class specification.
+ res.put(test, Collections.emptySet());
+ continue;
+ }
+
+ String lastTestPart = testParts[testParts.length - 1];
+ if (lastTestPart.isEmpty()) {
+ throw new IllegalArgumentException("Invalid test specifier: " + test);
+ }
+
+ char firstCharOfLastTestPart = lastTestPart.charAt(0);
+ if (!Character.isLowerCase(firstCharOfLastTestPart)) {
+ // The first character of the last test part is not lowercase, assume this is a fully qualified test
+ // class specification, e.g. org.foo.bar.TestClass.
+ res.put(test, Collections.emptySet());
+ }
+
+ // The first character of the last test part is lowercase, assume this is a test class *and* method name
+ // specification.
+ String testMethodName = lastTestPart;
+ int classPartsCount = testParts.length - 1;
+ String[] classParts = new String[classPartsCount];
+ System.arraycopy(testParts, 0, classParts, 0, classPartsCount);
+ String testClass = String.join(".", classParts);
+
+ res.compute(testClass, (k, v) -> {
+ if (v == null) {
+ v = new HashSet<>();
+ }
+ v.add(testMethodName);
+ return v;
+ });
+ }
+ return res;
+ }
+
+ private static Set getKey(Class> testClass, Map> testsMap) {
+ String className = testClass.getName();
+ if (testsMap.containsKey(className)) {
+ return testsMap.get(className);
+ }
+
+ String unqualifiedClassName = testClass.getSimpleName();
+ if (testsMap.containsKey(unqualifiedClassName)) {
+ return testsMap.get(unqualifiedClassName);
+ }
+
+ return null;
+ }
+
+ private static boolean contains(Class extends AbstractSmackIntTest> testClass, Map> testsMap) {
+ Set enabledMethods = getKey(testClass, testsMap);
+ return enabledMethods != null;
+ }
+
+ public boolean isClassEnabled(Class extends AbstractSmackIntTest> testClass) {
+ if (enabledTestsMap.isEmpty()) {
+ return true;
+ }
+
+ return contains(testClass, enabledTestsMap);
+ }
+
+ public boolean isClassDisabled(Class extends AbstractSmackIntTest> testClass) {
+ if (disabledTestsMap.isEmpty()) {
+ return false;
+ }
+
+ return contains(testClass, disabledTestsMap);
+ }
+
+ private static boolean contains(Method method, Map> testsMap) {
+ Class> testClass = method.getDeclaringClass();
+ Set methods = getKey(testClass, testsMap);
+
+ if (methods == null) {
+ return false;
+ }
+
+ if (methods.isEmpty()) {
+ return true;
+ }
+
+ String methodName = method.getName();
+ return methods.contains(methodName);
+ }
+
+ public boolean isMethodEnabled(Method method) {
+ if (enabledTestsMap.isEmpty()) {
+ return true;
+ }
+
+ return contains(method, enabledTestsMap);
+ }
+
+ public boolean isMethodDisabled(Method method) {
+ if (disabledTestsMap.isEmpty()) {
+ return false;
+ }
+
+ return contains(method, disabledTestsMap);
+ }
+
}
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java
index 769146c6f..433386600 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java
@@ -282,13 +282,13 @@ public class SmackIntegrationTestFramework {
continue;
}
- if (config.enabledTests != null && !isInSet(testClass, config.enabledTests)) {
+ if (!config.isClassEnabled(testClass)) {
DisabledTestClass disabledTestClass = new DisabledTestClass(testClass, "Skipping test class " + testClassName + " because it is not enabled");
testRunResult.disabledTestClasses.add(disabledTestClass);
continue;
}
- if (isInSet(testClass, config.disabledTests)) {
+ if (config.isClassDisabled(testClass)) {
DisabledTestClass disabledTestClass = new DisabledTestClass(testClass, "Skipping test class " + testClassName + " because it is disalbed");
testRunResult.disabledTestClasses.add(disabledTestClass);
continue;
@@ -377,14 +377,13 @@ public class SmackIntegrationTestFramework {
while (it.hasNext()) {
final Method method = it.next();
final String methodName = method.getName();
- if (config.enabledTests != null && !(config.enabledTests.contains(methodName)
- || isInSet(testClass, config.enabledTests))) {
+ if (!config.isMethodEnabled(method)) {
DisabledTest disabledTest = new DisabledTest(method, "Skipping test method " + methodName + " because it is not enabled");
testRunResult.disabledTests.add(disabledTest);
it.remove();
continue;
}
- if (config.disabledTests != null && config.disabledTests.contains(methodName)) {
+ if (config.isMethodDisabled(method)) {
DisabledTest disabledTest = new DisabledTest(method, "Skipping test method " + methodName + " because it is disabled");
testRunResult.disabledTests.add(disabledTest);
it.remove();
@@ -607,15 +606,6 @@ public class SmackIntegrationTestFramework {
return (Exception) e;
}
- private static boolean isInSet(Class> clz, Set classes) {
- if (classes == null) {
- return false;
- }
- final String className = clz.getName();
- final String unqualifiedClassName = clz.getSimpleName();
- return classes.contains(className) || classes.contains(unqualifiedClassName);
- }
-
public static final class TestRunResult {
/**
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/IntegrationTestRosterUtil.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/IntegrationTestRosterUtil.java
index c03ba1850..4a6569dcf 100644
--- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/IntegrationTestRosterUtil.java
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/IntegrationTestRosterUtil.java
@@ -24,6 +24,7 @@ import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.packet.Presence;
+import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.roster.AbstractPresenceEventListener;
import org.jivesoftware.smack.roster.PresenceEventListener;
import org.jivesoftware.smack.roster.Roster;
@@ -99,7 +100,16 @@ public class IntegrationTestRosterUtil {
if (c2Entry == null) {
return;
}
- roster.removeEntry(c2Entry);
+ try {
+ roster.removeEntry(c2Entry);
+ } catch (XMPPErrorException e) {
+ // Account for race conditions: server-sided, the item might already have been removed.
+ if (e.getStanzaError().getCondition() == StanzaError.Condition.item_not_found) {
+ // Trying to remove non-existing item. As it needs to be gone, this is fine.
+ return;
+ }
+ throw e;
+ }
}
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java
index 32f1d32b3..7e149ff3b 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java
@@ -23,8 +23,9 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
-import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smackx.disco.EntityCapabilitiesChangedListener;
+import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.geoloc.GeoLocationManager;
import org.jivesoftware.smackx.geoloc.packet.GeoLocation;
import org.jivesoftware.smackx.pep.PepEventListener;
@@ -35,7 +36,7 @@ import org.igniterealtime.smack.inttest.annotations.AfterClass;
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
-import org.jxmpp.jid.EntityBareJid;
+import org.junit.jupiter.api.Assertions;
import org.jxmpp.util.XmppDateTime;
public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest {
@@ -49,10 +50,21 @@ public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest {
glm2 = GeoLocationManager.getInstanceFor(conTwo);
}
+ @AfterClass
+ public void unsubscribe() throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
+ IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
+ }
+
+ /**
+ * Verifies that a notification is sent when a publication is received, assuming that notification filtering
+ * has been adjusted to allow for the notification to be delivered.
+ *
+ * @throws Exception if the test fails
+ */
@SmackIntegrationTest
- public void test() throws TimeoutException, Exception {
+ public void testNotification() throws Exception {
GeoLocation.Builder builder = GeoLocation.builder();
- GeoLocation geoLocation1 = builder.setAccuracy(23d)
+ GeoLocation data = builder.setAccuracy(23d)
.setAlt(1000d)
.setAltAccuracy(10d)
.setArea("Delhi")
@@ -77,31 +89,163 @@ public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest {
.build();
IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
- final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint();
- final PepEventListener geoLocationListener = new PepEventListener() {
- @Override
- public void onPepEvent(EntityBareJid jid, GeoLocation geoLocation, String id, Message message) {
- if (geoLocation.equals(geoLocation1)) {
- geoLocationReceived.signal();
- } else {
- geoLocationReceived.signalFailure("Received non matching GeoLocation");
- }
+ final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint();
+
+ final PepEventListener geoLocationListener = (jid, geoLocation, id, message) -> {
+ if (geoLocation.equals(data)) {
+ geoLocationReceived.signal();
}
};
- glm2.addGeoLocationListener(geoLocationListener);
-
try {
- glm1.publishGeoLocation(geoLocation1);
- geoLocationReceived.waitForResult(timeout);
+ // Register ConTwo's interest in receiving geolocation notifications, and wait for that interest to have been propagated.
+ registerListenerAndWait(glm2, ServiceDiscoveryManager.getInstanceFor(conTwo), geoLocationListener);
+
+ // Publish the data.
+ glm1.publishGeoLocation(data); // for the purpose of this test, this needs not be blocking/use publishAndWait();
+
+ // Wait for the data to be received.
+ try {
+ Object result = geoLocationReceived.waitForResult(timeout);
+
+ // Explicitly assert the success case.
+ Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
+ } catch (TimeoutException e) {
+ Assertions.fail("Expected to receive a PEP notification, but did not.");
+ }
} finally {
- glm2.removeGeoLocationListener(geoLocationListener);
+ unregisterListener(glm2, geoLocationListener);
}
}
- @AfterClass
- public void unsubscribe() throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
+ /**
+ * Verifies that a notification for a previously sent publication is received as soon as notification filtering
+ * has been adjusted to allow for the notification to be delivered.
+ *
+ * @throws Exception if the test fails
+ */
+ @SmackIntegrationTest
+ public void testNotificationAfterFilterChange() throws Exception {
+ GeoLocation.Builder builder = GeoLocation.builder();
+ GeoLocation data = builder.setAccuracy(12d)
+ .setAlt(999d)
+ .setAltAccuracy(9d)
+ .setArea("Amsterdam")
+ .setBearing(9d)
+ .setBuilding("Test Building")
+ .setCountry("Netherlands")
+ .setCountryCode("NL")
+ .setDescription("My Description")
+ .setFloor("middle")
+ .setLat(25.098345d)
+ .setLocality("brilliant")
+ .setLon(77.992034)
+ .setPostalcode("110085")
+ .setRegion("North")
+ .setRoom("small")
+ .setSpeed(250.0d)
+ .setStreet("Wall Street")
+ .setText("Unit Testing GeoLocation 2")
+ .setTimestamp(XmppDateTime.parseDate("2007-02-19"))
+ .setTzo("+5:30")
+ .setUri(new URI("http://xmpp.org"))
+ .build();
+
+ IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
+
+ final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint();
+
+ final PepEventListener geoLocationListener = (jid, geoLocation, id, message) -> {
+ if (geoLocation.equals(data)) {
+ geoLocationReceived.signal();
+ }
+ };
+
+ // TODO Ensure that pre-existing filtering notification excludes geolocation.
+ try {
+ // Publish the data
+ publishAndWait(glm1, ServiceDiscoveryManager.getInstanceFor(conOne), data);
+
+ // Adds listener, which implicitly publishes a disco/info filter for geolocation notification.
+ registerListenerAndWait(glm2, ServiceDiscoveryManager.getInstanceFor(conTwo), geoLocationListener);
+
+ // Wait for the data to be received.
+ try {
+ Object result = geoLocationReceived.waitForResult(timeout);
+
+ // Explicitly assert the success case.
+ Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
+ } catch (TimeoutException e) {
+ Assertions.fail("Expected to receive a PEP notification, but did not.");
+ }
+ } finally {
+ unregisterListener(glm2, geoLocationListener);
+ }
+ }
+
+ /**
+ * Registers a listener for GeoLocation data. This implicitly publishes a CAPS update to include a notification
+ * filter for the geolocation node. This method blocks until the server has indicated that this update has been
+ * received.
+ *
+ * @param geoManager The GeoLocationManager instance for the connection that is expected to receive data.
+ * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
+ * @param listener A listener instance for GeoLocation data that is to be registered.
+ *
+ * @throws Exception if the test fails
+ */
+ public void registerListenerAndWait(GeoLocationManager geoManager, ServiceDiscoveryManager discoManager, PepEventListener listener) throws Exception {
+ final SimpleResultSyncPoint notificationFilterReceived = new SimpleResultSyncPoint();
+ final EntityCapabilitiesChangedListener notificationFilterReceivedListener = info -> {
+ if (info.containsFeature(GeoLocationManager.GEOLOCATION_NODE + "+notify")) {
+ notificationFilterReceived.signal();
+ }
+ };
+
+ discoManager.addEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
+ try {
+ geoManager.addGeoLocationListener(listener);
+ notificationFilterReceived.waitForResult(timeout);
+ } finally {
+ discoManager.removeEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
+ }
+ }
+
+ /**
+ * The functionally reverse of {@link #registerListenerAndWait(GeoLocationManager, ServiceDiscoveryManager, PepEventListener)}
+ * with the difference of not being a blocking operation.
+ *
+ * @param geoManager The GeoLocationManager instance for the connection that was expected to receive data.
+ * @param listener A listener instance for GeoLocation data that is to be removed.
+ */
+ public void unregisterListener(GeoLocationManager geoManager, PepEventListener listener) {
+ // Does it make sense to have a method implementation that's one line? This is provided to allow for symmetry in the API.
+ geoManager.removeGeoLocationListener(listener);
+ }
+
+ /**
+ * Publish data using PEP, and block until the server has echoed the publication back to the publishing user.
+ *
+ * @param geoManager The GeoLocationManager instance for the connection that is expected to publish data.
+ * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
+ * @param data The data to be published.
+ *
+ * @throws Exception if the test fails
+ */
+ public void publishAndWait(GeoLocationManager geoManager, ServiceDiscoveryManager discoManager, GeoLocation data) throws Exception {
+ final SimpleResultSyncPoint publicationEchoReceived = new SimpleResultSyncPoint();
+ final PepEventListener publicationEchoListener = (jid, geoLocation, id, message) -> {
+ if (geoLocation.equals(data)) {
+ publicationEchoReceived.signal();
+ }
+ };
+ try {
+ registerListenerAndWait(geoManager, discoManager, publicationEchoListener);
+ geoManager.addGeoLocationListener(publicationEchoListener);
+ geoManager.publishGeoLocation(data);
+ } finally {
+ geoManager.removeGeoLocationListener(publicationEchoListener);
+ }
}
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java
index d2eff1080..1a83085dc 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java
@@ -16,9 +16,13 @@
*/
package org.jivesoftware.smackx.mood;
+import java.util.concurrent.TimeoutException;
+
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smackx.disco.EntityCapabilitiesChangedListener;
+import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.mood.element.MoodElement;
import org.jivesoftware.smackx.pep.PepEventListener;
@@ -28,6 +32,7 @@ import org.igniterealtime.smack.inttest.annotations.AfterClass;
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
+import org.junit.jupiter.api.Assertions;
public class MoodIntegrationTest extends AbstractSmackIntegrationTest {
@@ -40,32 +45,155 @@ public class MoodIntegrationTest extends AbstractSmackIntegrationTest {
mm2 = MoodManager.getInstanceFor(conTwo);
}
- @SmackIntegrationTest
- public void test() throws Exception {
- IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
-
- final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint();
-
- final PepEventListener moodListener = (jid, moodElement, id, message) -> {
- if (moodElement.getMood() == Mood.satisfied) {
- moodReceived.signal();
- }
- };
- mm2.addMoodListener(moodListener);
-
- try {
- mm1.setMood(Mood.satisfied);
-
- moodReceived.waitForResult(timeout);
- } finally {
- mm2.removeMoodListener(moodListener);
- }
- }
-
@AfterClass
public void unsubscribe()
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
}
+
+ /**
+ * Verifies that a notification is sent when a publication is received, assuming that notification filtering
+ * has been adjusted to allow for the notification to be delivered.
+ *
+ * @throws Exception if the test fails
+ */
+ @SmackIntegrationTest
+ public void testNotification() throws Exception {
+ Mood data = Mood.satisfied;
+
+ IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
+
+ final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint();
+
+ final PepEventListener moodListener = (jid, moodElement, id, message) -> {
+ if (moodElement.getMood().equals(data)) {
+ moodReceived.signal();
+ }
+ };
+
+ try {
+ // Register ConTwo's interest in receiving mood notifications, and wait for that interest to have been propagated.
+ registerListenerAndWait(mm2, ServiceDiscoveryManager.getInstanceFor(conTwo), moodListener);
+
+ // Publish the data.
+ mm1.setMood(data); // for the purpose of this test, this needs not be blocking/use publishAndWait();
+
+ // Wait for the data to be received.
+ try {
+ moodReceived.waitForResult(timeout);
+ } catch (TimeoutException e) {
+ Assertions.fail("Expected to receive a PEP notification, but did not.");
+ }
+ } finally {
+ unregisterListener(mm2, moodListener);
+ }
+ }
+
+ /**
+ * Verifies that a notification for a previously sent publication is received as soon as notification filtering
+ * has been adjusted to allow for the notification to be delivered.
+ *
+ * @throws Exception if the test fails
+ */
+ @SmackIntegrationTest
+ public void testNotificationAfterFilterChange() throws Exception {
+ Mood data = Mood.cautious;
+
+ IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
+
+ final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint();
+
+ final PepEventListener moodListener = (jid, moodElement, id, message) -> {
+ if (moodElement.getMood().equals(data)) {
+ moodReceived.signal();
+ }
+ };
+
+ // TODO Ensure that pre-existing filtering notification excludes mood.
+ try {
+ // Publish the data
+ publishAndWait(mm1, ServiceDiscoveryManager.getInstanceFor(conOne), data);
+
+ // Adds listener, which implicitly publishes a disco/info filter for mood notification.
+ registerListenerAndWait(mm2, ServiceDiscoveryManager.getInstanceFor(conTwo), moodListener);
+
+ // Wait for the data to be received.
+ try {
+ Object result = moodReceived.waitForResult(timeout);
+
+ // Explicitly assert the success case.
+ Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
+ } catch (TimeoutException e) {
+ Assertions.fail("Expected to receive a PEP notification, but did not.");
+ }
+ } finally {
+ unregisterListener(mm2, moodListener);
+ }
+ }
+
+ /**
+ * Registers a listener for User Tune data. This implicitly publishes a CAPS update to include a notification
+ * filter for the mood node. This method blocks until the server has indicated that this update has been
+ * received.
+ *
+ * @param moodManager The MoodManager instance for the connection that is expected to receive data.
+ * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
+ * @param listener A listener instance for Mood data that is to be registered.
+ *
+ * @throws Exception if the test fails
+ */
+ public void registerListenerAndWait(MoodManager moodManager, ServiceDiscoveryManager discoManager, PepEventListener listener) throws Exception {
+ final SimpleResultSyncPoint notificationFilterReceived = new SimpleResultSyncPoint();
+ final EntityCapabilitiesChangedListener notificationFilterReceivedListener = info -> {
+ if (info.containsFeature(MoodManager.MOOD_NODE + "+notify")) {
+ notificationFilterReceived.signal();
+ }
+ };
+
+ discoManager.addEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
+ try {
+ moodManager.addMoodListener(listener);
+ notificationFilterReceived.waitForResult(timeout);
+ } finally {
+ discoManager.removeEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
+ }
+ }
+
+ /**
+ * The functionally reverse of {@link #registerListenerAndWait(MoodManager, ServiceDiscoveryManager, PepEventListener)}
+ * with the difference of not being a blocking operation.
+ *
+ * @param moodManager The MoodManager instance for the connection that was expected to receive data.
+ * @param listener A listener instance for Mood data that is to be removed.
+ */
+ public void unregisterListener(MoodManager moodManager, PepEventListener listener) {
+ // Does it make sense to have a method implementation that's one line? This is provided to allow for symmetry in the API.
+ moodManager.removeMoodListener(listener);
+ }
+
+ /**
+ * Publish data using PEP, and block until the server has echoed the publication back to the publishing user.
+ *
+ * @param moodManager The MoodManager instance for the connection that is expected to publish data.
+ * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
+ * @param data The data to be published.
+ *
+ * @throws Exception if the test fails
+ */
+ public void publishAndWait(MoodManager moodManager, ServiceDiscoveryManager discoManager, Mood data) throws Exception {
+ final SimpleResultSyncPoint publicationEchoReceived = new SimpleResultSyncPoint();
+ final PepEventListener publicationEchoListener = (jid, moodElement, id, message) -> {
+ if (moodElement.getMood().equals(data)) {
+ publicationEchoReceived.signal();
+ }
+ };
+ try {
+ registerListenerAndWait(moodManager, discoManager, publicationEchoListener);
+ moodManager.addMoodListener(publicationEchoListener);
+ moodManager.setMood(data);
+ } finally {
+ moodManager.removeMoodListener(publicationEchoListener);
+ }
+ }
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java
index 597b767e6..5b2879c63 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java
@@ -22,8 +22,6 @@ import java.util.List;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.util.StringUtils;
-import org.jivesoftware.smackx.xdata.form.FillableForm;
-import org.jivesoftware.smackx.xdata.form.Form;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
@@ -57,9 +55,8 @@ public class AbstractMultiUserChatIntegrationTest extends AbstractSmackIntegrati
if (services.isEmpty()) {
throw new TestNotPossibleException("No MUC (XEP-45) service found");
}
- else {
- mucService = services.get(0);
- }
+
+ mucService = services.get(0);
}
/**
@@ -90,18 +87,57 @@ public class AbstractMultiUserChatIntegrationTest extends AbstractSmackIntegrati
muc.destroy("test fixture teardown", null);
}
- static void createMuc(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException {
- MultiUserChat.MucCreateConfigFormHandle handle = muc.create(Resourcepart.from(resourceName));
- if (handle != null) {
- handle.makeInstant();
- }
+ static void createMuc(MultiUserChat muc, Resourcepart resourceName) throws
+ SmackException.NoResponseException, XMPPException.XMPPErrorException,
+ InterruptedException, MultiUserChatException.MucAlreadyJoinedException,
+ SmackException.NotConnectedException,
+ MultiUserChatException.MissingMucCreationAcknowledgeException,
+ MultiUserChatException.NotAMucServiceException {
+ muc.create(resourceName).makeInstant();
}
- static void createModeratedMuc(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException {
- muc.create(Resourcepart.from(resourceName));
- Form configForm = muc.getConfigurationForm();
- FillableForm answerForm = configForm.getFillableForm();
- answerForm.setAnswer("muc#roomconfig_moderatedroom", true); //TODO Add this to the MucConfigFormManager?
- muc.sendConfigurationForm(answerForm);
+ static void createMuc(MultiUserChat muc, String nickname) throws
+ XmppStringprepException, MultiUserChatException.MucAlreadyJoinedException,
+ XMPPException.XMPPErrorException, SmackException.NotConnectedException,
+ MultiUserChatException.MissingMucCreationAcknowledgeException,
+ SmackException.NoResponseException, InterruptedException,
+ MultiUserChatException.NotAMucServiceException {
+ createMuc(muc, Resourcepart.from(nickname));
+ }
+
+ static void createMembersOnlyMuc(MultiUserChat muc, Resourcepart resourceName) throws
+ SmackException.NoResponseException, XMPPException.XMPPErrorException,
+ InterruptedException, MultiUserChatException.MucAlreadyJoinedException,
+ SmackException.NotConnectedException,
+ MultiUserChatException.MissingMucCreationAcknowledgeException,
+ MultiUserChatException.MucConfigurationNotSupportedException,
+ MultiUserChatException.NotAMucServiceException {
+ muc.create(resourceName)
+ .getConfigFormManager()
+ .makeMembersOnly()
+ .submitConfigurationForm();
+ }
+
+ static void createModeratedMuc(MultiUserChat muc, Resourcepart resourceName)
+ throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException,
+ MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException,
+ MultiUserChatException.MissingMucCreationAcknowledgeException,
+ MultiUserChatException.NotAMucServiceException,
+ MultiUserChatException.MucConfigurationNotSupportedException {
+ muc.create(resourceName)
+ .getConfigFormManager()
+ .makeModerated()
+ .submitConfigurationForm();
+ }
+
+ static void createHiddenMuc(MultiUserChat muc, Resourcepart resourceName)
+ throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException,
+ MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException,
+ MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException,
+ MultiUserChatException.MucConfigurationNotSupportedException {
+ muc.create(resourceName)
+ .getConfigFormManager()
+ .makeHidden()
+ .submitConfigurationForm();
}
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatEntityIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatEntityIntegrationTest.java
new file mode 100644
index 000000000..279160c0f
--- /dev/null
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatEntityIntegrationTest.java
@@ -0,0 +1,205 @@
+/**
+ *
+ * Copyright 2021 Dan Caseley
+ *
+ * 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.muc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Map;
+
+import org.jivesoftware.smack.SmackException;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.StanzaError;
+import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
+import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
+import org.jivesoftware.smackx.disco.packet.DiscoverItems;
+
+import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
+import org.igniterealtime.smack.inttest.TestNotPossibleException;
+import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
+import org.jxmpp.jid.EntityBareJid;
+import org.jxmpp.jid.EntityFullJid;
+import org.jxmpp.jid.parts.Resourcepart;
+
+public class MultiUserChatEntityIntegrationTest extends AbstractMultiUserChatIntegrationTest {
+
+ public MultiUserChatEntityIntegrationTest(SmackIntegrationTestEnvironment environment)
+ throws SmackException.NoResponseException, XMPPException.XMPPErrorException,
+ SmackException.NotConnectedException, InterruptedException, TestNotPossibleException {
+ super(environment);
+ }
+
+ /**
+ * Asserts that a MUC service can have its features discovered
+ *
+ * From XEP-0045 § 6.2:
+ *
+ * An entity may wish to discover if a service implements the Multi-User Chat protocol; in order to do so, it
+ * sends a service discovery information ("disco#info") query to the MUC service's JID. The service MUST return
+ * its identity and the features it supports.
+ *
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest
+ public void mucTestForDiscoveringFeatures() throws Exception {
+ DiscoverInfo info = mucManagerOne.getMucServiceDiscoInfo(mucManagerOne.getMucServiceDomains().get(0));
+ assertTrue(info.getIdentities().size() > 0);
+ assertTrue(info.getFeatures().size() > 0);
+ }
+
+ /**
+ * Asserts that a MUC Service lists its public rooms.
+ *
+ * From XEP-0045 § 6.3:
+ *
+ * The service discovery items ("disco#items") protocol enables an entity to query a service for a list of
+ * associated items, which in the case of a chat service would consist of the specific chat rooms hosted by the
+ * service. The service SHOULD return a full list of the public rooms it hosts (i.e., not return any rooms that
+ * are hidden).
+ *
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest
+ public void mucTestForDiscoveringRooms() throws Exception {
+ EntityBareJid mucAddressPublic = getRandomRoom("smack-inttest-publicroom");
+ MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddressPublic);
+
+ EntityBareJid mucAddressHidden = getRandomRoom("smack-inttest-hiddenroom");
+ MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddressHidden);
+
+ createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString));
+
+ Map rooms;
+ try {
+ createHiddenMuc(mucAsSeenByTwo, Resourcepart.from("two-" + randomString));
+ rooms = mucManagerThree.getRoomsHostedBy(mucService);
+ } finally {
+ tryDestroy(mucAsSeenByOne);
+ tryDestroy(mucAsSeenByTwo);
+ }
+
+ assertTrue(rooms.containsKey(mucAddressPublic));
+ assertFalse(rooms.containsKey(mucAddressHidden));
+ }
+
+ /**
+ * Asserts that a MUC Service returns disco info for a room.
+ *
+ * From XEP-0045 § 6.4:
+ *
+ * Using the disco#info protocol, an entity may also query a specific chat room for more detailed information
+ * about the room....The room MUST return its identity and SHOULD return the features it supports
+ *
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest
+ public void mucTestForDiscoveringRoomInfo() throws Exception {
+ EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoinfo");
+ MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
+ createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString));
+
+ DiscoverInfo discoInfo;
+ try {
+ // Use SDM because mucManagerOne.getRoomInfo(mucAddress) might not use Disco
+ discoInfo = ServiceDiscoveryManager.getInstanceFor(conOne).discoverInfo(mucAddress);
+ } finally {
+ tryDestroy(mucAsSeenByOne);
+ }
+
+ assertTrue(discoInfo.getIdentities().size() > 0);
+ assertTrue(discoInfo.getFeatures().size() > 0);
+ }
+
+ /**
+ * Asserts that a MUC Service returns disco info for a room's items.
+ *
+ * From XEP-0045 § 6.5:
+ *
+ * An entity MAY also query a specific chat room for its associated items. An implementation MAY return a list
+ * of existing occupants if that information is publicly available, or return no list at all if this information is
+ * kept private.
+ *
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest
+ public void mucTestForDiscoveringRoomItems() throws Exception {
+ EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoitems");
+ MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
+ createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString));
+
+ DiscoverItems roomItems;
+ try {
+ roomItems = ServiceDiscoveryManager.getInstanceFor(conTwo).discoverItems(mucAddress);
+ } finally {
+ tryDestroy(mucAsSeenByOne);
+ }
+
+ assertEquals(1, roomItems.getItems().size());
+ }
+
+ /**
+ * Asserts that a non-occupant receives a Bad Request error when attempting to query an occupant by their
+ * occupant JID.
+ *
+ * From XEP-0045 § 6.6:
+ *
+ * If a non-occupant attempts to send a disco request to an address of the form <room@service/nick>, a MUC service
+ * MUST return a <bad-request/> error
+ *
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest
+ public void mucTestForRejectingDiscoOnRoomOccupantByNonOccupant() throws Exception {
+ EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoitems");
+ MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
+ final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString);
+ createMuc(mucAsSeenByOne, nicknameOne);
+ final EntityFullJid mucAsSeenByOneUserJid = mucAsSeenByOne.getMyRoomJid();
+ // Ensure that we do not invoke discoverItems() with null below. This should not happen, as the room JID should
+ // be non-null after we created and joined the room. But it can not hurt to explicitly test for it either.
+ if (mucAsSeenByOneUserJid == null) {
+ throw new AssertionError();
+ }
+
+ XMPPException.XMPPErrorException xe;
+ try {
+ xe = assertThrows(XMPPException.XMPPErrorException.class,
+ () -> ServiceDiscoveryManager.getInstanceFor(conTwo).discoverItems(mucAsSeenByOneUserJid));
+ } finally {
+ tryDestroy(mucAsSeenByOne);
+ }
+
+ final StanzaError.Condition expectedCondition;
+ switch (sinttestConfiguration.compatibilityMode) {
+ default:
+ expectedCondition = StanzaError.Condition.bad_request;
+ break;
+ case ejabberd:
+ expectedCondition = StanzaError.Condition.not_acceptable;
+ break;
+ }
+ assertEquals(xe.getStanzaError().getCondition(), expectedCondition);
+ }
+}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java
index 966ec3e97..d26e22593 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java
@@ -1,6 +1,6 @@
/**
*
- * Copyright 2021 Florian Schmaus
+ * Copyright 2021 Florian Schmaus, Dan Caseley
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.jivesoftware.smack.SmackException;
@@ -35,6 +36,7 @@ import org.igniterealtime.smack.inttest.util.ResultSyncPoint;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.Jid;
+import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart;
@@ -80,13 +82,14 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
});
createMuc(mucAsSeenByOne, "one-" + randomString);
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
-
- // This implicitly tests "The service MUST add the user to the moderator list and then inform the admin of
- // success" in §9.6, since it'll throw on either an error IQ or on no response.
- mucAsSeenByOne.grantModerator(nicknameTwo);
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+
+ // This implicitly tests "The service MUST add the user to the moderator list and then inform the admin of
+ // success" in §9.6, since it'll throw on either an error IQ or on no response.
+ mucAsSeenByOne.grantModerator(nicknameTwo);
+
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
@@ -128,13 +131,13 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
});
createMuc(mucAsSeenByOne, "one-" + randomString);
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
- mucAsSeenByThree.join(nicknameThree);
-
- mucAsSeenByOne.grantModerator(nicknameTwo);
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByThree.join(nicknameThree);
+
+ mucAsSeenByOne.grantModerator(nicknameTwo);
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
@@ -176,12 +179,12 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
});
createMuc(mucAsSeenByOne, "one-" + randomString);
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
-
- mucAsSeenByOne.grantModerator(nicknameTwo);
- mucAsSeenByOne.revokeModerator(nicknameTwo);
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+
+ mucAsSeenByOne.grantModerator(nicknameTwo);
+ mucAsSeenByOne.revokeModerator(nicknameTwo);
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
@@ -223,14 +226,14 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
});
createMuc(mucAsSeenByOne, "one-" + randomString);
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
- mucAsSeenByThree.join(nicknameThree);
-
- mucAsSeenByOne.grantModerator(nicknameTwo);
- mucAsSeenByOne.revokeModerator(nicknameTwo);
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByThree.join(nicknameThree);
+
+ mucAsSeenByOne.grantModerator(nicknameTwo);
+ mucAsSeenByOne.revokeModerator(nicknameTwo);
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
@@ -271,10 +274,10 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
});
createMuc(mucAsSeenByOne, "one-" + randomString);
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
- mucAsSeenByOne.revokeVoice(nicknameTwo);
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByOne.revokeVoice(nicknameTwo);
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
@@ -316,13 +319,13 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
});
createMuc(mucAsSeenByOne, "one-" + randomString);
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
- mucAsSeenByThree.join(nicknameThree);
-
- mucAsSeenByOne.revokeVoice(nicknameTwo);
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByThree.join(nicknameThree);
+
+ mucAsSeenByOne.revokeVoice(nicknameTwo);
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
@@ -364,12 +367,12 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
});
createMuc(mucAsSeenByOne, "one-" + randomString);
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
-
- // This implicitly tests "The service MUST add the user to the admin list and then inform the owner of success" in §10.6, since it'll throw on either an error IQ or on no response.
- mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+
+ // This implicitly tests "The service MUST add the user to the admin list and then inform the owner of success" in §10.6, since it'll throw on either an error IQ or on no response.
+ mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
@@ -412,13 +415,13 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
});
createMuc(mucAsSeenByOne, "one-" + randomString);
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
- mucAsSeenByThree.join(nicknameThree);
-
- mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByThree.join(nicknameThree);
+
+ mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
@@ -459,13 +462,12 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
});
createMuc(mucAsSeenByOne, "one-" + randomString);
-
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
-
- mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
- mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid());
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+
+ mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
+ mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid());
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
@@ -507,14 +509,14 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
});
createMuc(mucAsSeenByOne, "one-" + randomString);
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
- mucAsSeenByThree.join(nicknameThree);
- mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
-
- mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid());
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByThree.join(nicknameThree);
+ mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
+
+ mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid());
resultSyncPoint.waitForResult(timeout);
} finally {
tryDestroy(mucAsSeenByOne);
@@ -544,14 +546,14 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
createMuc(mucAsSeenByOne, "one-" + randomString);
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
-
- final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>();
- mucAsSeenByTwo.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence));
-
- mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test.");
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+
+ final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>();
+ mucAsSeenByTwo.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence));
+
+ mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test.");
Presence kickPresence = resultSyncPoint.waitForResult(timeout);
MUCUser mucUser = MUCUser.from(kickPresence);
assertNotNull(mucUser);
@@ -591,16 +593,16 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
createMuc(mucAsSeenByOne, "one-" + randomString);
- final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
- final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
- mucAsSeenByTwo.join(nicknameTwo);
- mucAsSeenByThree.join(nicknameThree);
-
- final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>();
- mucAsSeenByThree.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence));
-
- mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test.");
try {
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByThree.join(nicknameThree);
+
+ final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>();
+ mucAsSeenByThree.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence));
+
+ mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test.");
Presence kickPresence = resultSyncPoint.waitForResult(timeout);
MUCUser mucUser = MUCUser.from(kickPresence);
assertNotNull(mucUser);
@@ -619,4 +621,299 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs
}
+ /**
+ * Asserts that an affiliation is persistent between visits to the room.
+ *
+ * From XEP-0045 § 5.2:
+ *
+ * These affiliations are long-lived in that they persist across a user's visits to the room and are not affected
+ * by happenings in the room...Affiliations are granted, revoked, and maintained based on the user's bare JID, not
+ * the nick as with roles.
+ *
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest
+ public void mucTestPersistentAffiliation() throws Exception {
+ EntityBareJid mucAddress = getRandomRoom("smack-inttest");
+
+ MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
+
+ final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString);
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
+
+ createMuc(mucAsSeenByOne, nicknameOne);
+ try {
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByThree.join(nicknameThree);
+
+ mucAsSeenByOne.grantOwnership(conTwo.getUser().asBareJid());
+ mucAsSeenByOne.grantAdmin(conThree.getUser().asBareJid());
+
+ mucAsSeenByTwo.leave();
+ mucAsSeenByThree.leave();
+ Presence p2 = mucAsSeenByTwo.join(nicknameTwo);
+ Presence p3 = mucAsSeenByThree.join(nicknameThree);
+ assertEquals(MUCAffiliation.owner, MUCUser.from(p2).getItem().getAffiliation());
+ assertEquals(MUCAffiliation.admin, MUCUser.from(p3).getItem().getAffiliation());
+ } finally {
+ tryDestroy(mucAsSeenByOne);
+ }
+ }
+
+ /**
+ * Asserts that a moderator cannot revoke voice from an owner
+ *
+ * From XEP-0045 § 5.1.1:
+ *
+ * A moderator MUST NOT be able to revoke voice privileges from an admin or owner
+ *
+ *
+ * From XEP-0045 § 8.4:
+ *
+ * A moderator MUST NOT be able to revoke voice from a user whose affiliation is at or above the moderator's level.
+ * In addition, a service MUST NOT allow the voice privileges of an admin or owner to be removed by anyone. If a
+ * moderator attempts to revoke voice privileges from such a user, the service MUST deny the request and return a
+ * <not-allowed/> error to the sender along with the offending item(s)
+ *
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest
+ public void mucTestModeratorCannotRevokeVoiceFromOwner() throws Exception {
+ EntityBareJid mucAddress = getRandomRoom("smack-inttest");
+
+ MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
+
+ final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString);
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+
+ createModeratedMuc(mucAsSeenByOne, nicknameOne);
+ try {
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByOne.grantModerator(nicknameTwo);
+ XMPPException.XMPPErrorException xe = assertThrows(XMPPException.XMPPErrorException.class,
+ () -> mucAsSeenByTwo.revokeVoice(nicknameOne));
+ assertEquals(xe.getStanzaError().getCondition().toString(), "not-allowed");
+ } finally {
+ tryDestroy(mucAsSeenByOne);
+ }
+ }
+
+ /**
+ * Asserts that a moderator cannot revoke moderator privileges from a moderator with a higher affiliation
+ * than themselves.
+ *
+ * From XEP-0045 § 5.1.3 and §5.2.1:
+ *
+ * A moderator SHOULD NOT be allowed to revoke moderation privileges from someone with a higher affiliation than
+ * themselves (i.e., an unaffiliated moderator SHOULD NOT be allowed to revoke moderation privileges from an admin
+ * or an owner, and an admin SHOULD NOT be allowed to revoke moderation privileges from an owner)
+ *
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest
+ public void mucTestModeratorCannotBeRevokedFromHigherAffiliation() throws Exception {
+ EntityBareJid mucAddress = getRandomRoom("smack-inttest");
+
+ MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
+
+ final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString);
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
+
+ createModeratedMuc(mucAsSeenByOne, nicknameOne);
+ try {
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByThree.join(nicknameThree);
+
+ mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
+ mucAsSeenByOne.grantModerator(nicknameThree);
+
+ // Admin cannot revoke from Owner
+ XMPPException.XMPPErrorException xe1 = assertThrows(XMPPException.XMPPErrorException.class,
+ () -> mucAsSeenByTwo.revokeModerator(nicknameOne));
+ // Moderator cannot revoke from Admin
+ XMPPException.XMPPErrorException xe2 = assertThrows(XMPPException.XMPPErrorException.class,
+ () -> mucAsSeenByThree.revokeModerator(nicknameOne));
+ // Moderator cannot revoke from Owner
+ XMPPException.XMPPErrorException xe3 = assertThrows(XMPPException.XMPPErrorException.class,
+ () -> mucAsSeenByThree.revokeModerator(nicknameTwo));
+ assertEquals(xe1.getStanzaError().getCondition().toString(), "not-allowed");
+ assertEquals(xe2.getStanzaError().getCondition().toString(), "not-allowed");
+ assertEquals(xe3.getStanzaError().getCondition().toString(), "not-allowed");
+ } finally {
+ tryDestroy(mucAsSeenByOne);
+ }
+ }
+
+ /**
+ * Asserts that an unmoderated room assigns the correct default roles for a given affiliation
+ *
+ * From XEP-0045 § 5.1.2:
+ *
+ * ...the initial default roles that a service SHOULD set based on the user's affiliation...
+ *
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest
+ public void mucTestDefaultRoleForAffiliationInUnmoderatedRoom() throws Exception {
+ EntityBareJid mucAddress = getRandomRoom("smack-inttest-unmoderatedroles");
+
+ MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
+
+ final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString);
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
+
+ createMuc(mucAsSeenByOne, nicknameOne);
+ try {
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByThree.join(nicknameThree);
+
+ final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>();
+ mucAsSeenByOne.addParticipantStatusListener(new ParticipantStatusListener() {
+ @Override
+ public void adminGranted(EntityFullJid participant) {
+ resultSyncPoint.signal("done");
+ }
+ });
+ mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
+ resultSyncPoint.waitForResult(timeout);
+
+ assertEquals(mucAsSeenByOne.getOccupantsCount(), 3);
+ assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant(
+ JidCreate.entityFullFrom(mucAddress, nicknameOne)).getRole());
+ assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant(
+ JidCreate.entityFullFrom(mucAddress, nicknameTwo)).getRole());
+ assertEquals(MUCRole.participant, mucAsSeenByOne.getOccupant(
+ JidCreate.entityFullFrom(mucAddress, nicknameThree)).getRole());
+ } finally {
+ tryDestroy(mucAsSeenByOne);
+ }
+ }
+
+ /**
+ * Asserts that a moderated room assigns the correct default roles for a given affiliation
+ *
+ * From XEP-0045 § 5.1.2:
+ *
+ * ...the initial default roles that a service SHOULD set based on the user's affiliation...
+ *
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest
+ public void mucTestDefaultRoleForAffiliationInModeratedRoom() throws Exception {
+ EntityBareJid mucAddress = getRandomRoom("smack-inttest-moderatedroles");
+
+ MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
+
+ final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString);
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
+
+ final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>();
+ mucAsSeenByOne.addParticipantStatusListener(new ParticipantStatusListener() {
+ @Override
+ public void adminGranted(EntityFullJid participant) {
+ resultSyncPoint.signal("done");
+ }
+ });
+
+ createModeratedMuc(mucAsSeenByOne, nicknameOne);
+
+ final MUCRole threeRole;
+ switch (sinttestConfiguration.compatibilityMode) {
+ default:
+ threeRole = MUCRole.visitor;
+ break;
+ case ejabberd:
+ threeRole = MUCRole.participant;
+ break;
+ }
+
+ try {
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByThree.join(nicknameThree);
+ mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
+ resultSyncPoint.waitForResult(timeout);
+
+ assertEquals(mucAsSeenByOne.getOccupantsCount(), 3);
+ assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant(
+ JidCreate.entityFullFrom(mucAddress, nicknameOne)).getRole());
+ assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant(
+ JidCreate.entityFullFrom(mucAddress, nicknameTwo)).getRole());
+ assertEquals(threeRole, mucAsSeenByOne.getOccupant(
+ JidCreate.entityFullFrom(mucAddress, nicknameThree)).getRole());
+ } finally {
+ tryDestroy(mucAsSeenByOne);
+ }
+ }
+
+ /**
+ * Asserts that a members-only room assigns the correct default roles for a given affiliation
+ *
+ * From XEP-0045 § 5.1.2:
+ *
+ * ...the initial default roles that a service SHOULD set based on the user's affiliation...
+ *
+ *
+ * @throws Exception when errors occur
+ */
+ @SmackIntegrationTest
+ public void mucTestDefaultRoleForAffiliationInMembersOnlyRoom() throws Exception {
+ EntityBareJid mucAddress = getRandomRoom("smack-inttest-membersonlyroles");
+
+ MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress);
+ MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress);
+
+ final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString);
+ final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString);
+ final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString);
+
+ final EntityFullJid jidOne = JidCreate.entityFullFrom(mucAddress, nicknameOne);
+ final EntityFullJid jidTwo = JidCreate.entityFullFrom(mucAddress, nicknameTwo);
+ final EntityFullJid jidThree = JidCreate.entityFullFrom(mucAddress, nicknameThree);
+
+ createMembersOnlyMuc(mucAsSeenByOne, nicknameOne);
+
+ final ResultSyncPoint adminResultSyncPoint = new ResultSyncPoint<>();
+ mucAsSeenByOne.addParticipantStatusListener(new ParticipantStatusListener() {
+ @Override
+ public void adminGranted(EntityFullJid participant) {
+ adminResultSyncPoint.signal("done");
+ }
+ });
+
+ try {
+ mucAsSeenByOne.grantMembership(conTwo.getUser().asBareJid());
+ mucAsSeenByOne.grantMembership(conThree.getUser().asBareJid());
+
+ mucAsSeenByTwo.join(nicknameTwo);
+ mucAsSeenByThree.join(nicknameThree);
+ mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid());
+ adminResultSyncPoint.waitForResult(timeout);
+ assertEquals(mucAsSeenByOne.getOccupantsCount(), 3);
+ assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant(jidOne).getRole());
+ assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant(jidTwo).getRole());
+ assertEquals(MUCRole.participant, mucAsSeenByOne.getOccupant(jidThree).getRole());
+ } finally {
+ tryDestroy(mucAsSeenByOne);
+ }
+ }
+
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/softwareInfo/SoftwareInfoIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/softwareInfo/SoftwareInfoIntegrationTest.java
index f289c96be..3bf111f53 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/softwareInfo/SoftwareInfoIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/softwareInfo/SoftwareInfoIntegrationTest.java
@@ -73,10 +73,10 @@ public class SoftwareInfoIntegrationTest extends AbstractSmackIntegrationTest {
.build();
SoftwareInfoForm softwareInfoForm = builder.setIcon(mediaElement)
- .setOS("Windows")
- .setOSVersion("XP")
- .setSoftware("Exodus")
- .setSoftwareVersion("0.9.1")
+ .setOS("Linux")
+ .setOSVersion("Debian")
+ .setSoftware("Gajim")
+ .setSoftwareVersion("1.4.0")
.build();
return softwareInfoForm;
}
diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java
index 0309f9af0..653bded72 100644
--- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java
+++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java
@@ -17,12 +17,14 @@
package org.jivesoftware.smackx.usertune;
import java.net.URI;
+import java.util.concurrent.TimeoutException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.XMPPException;
-import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smackx.disco.EntityCapabilitiesChangedListener;
+import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.pep.PepEventListener;
import org.jivesoftware.smackx.usertune.element.UserTuneElement;
@@ -32,7 +34,7 @@ import org.igniterealtime.smack.inttest.annotations.AfterClass;
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
-import org.jxmpp.jid.EntityBareJid;
+import org.junit.jupiter.api.Assertions;
public class UserTuneIntegrationTest extends AbstractSmackIntegrationTest {
@@ -45,46 +47,172 @@ public class UserTuneIntegrationTest extends AbstractSmackIntegrationTest {
utm2 = UserTuneManager.getInstanceFor(conTwo);
}
- @SmackIntegrationTest
- public void test() throws Exception {
- URI uri = new URI("http://www.yesworld.com/lyrics/Fragile.html#9");
- UserTuneElement.Builder builder = UserTuneElement.getBuilder();
- UserTuneElement userTuneElement1 = builder.setArtist("Yes")
- .setLength(686)
- .setRating(8)
- .setSource("Yessongs")
- .setTitle("Heart of the Sunrise")
- .setTrack("3")
- .setUri(uri)
- .build();
-
- IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
-
- final SimpleResultSyncPoint userTuneReceived = new SimpleResultSyncPoint();
-
- final PepEventListener userTuneListener = new PepEventListener() {
- @Override
- public void onPepEvent(EntityBareJid jid, UserTuneElement userTuneElement, String id, Message message) {
- if (userTuneElement.equals(userTuneElement1)) {
- userTuneReceived.signal();
- }
- }
- };
-
- utm2.addUserTuneListener(userTuneListener);
-
- try {
- utm1.publishUserTune(userTuneElement1);
- userTuneReceived.waitForResult(timeout);
- } finally {
- utm2.removeUserTuneListener(userTuneListener);
- }
- }
-
@AfterClass
public void unsubscribe()
throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException {
IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo);
}
+
+ /**
+ * Verifies that a notification is sent when a publication is received, assuming that notification filtering
+ * has been adjusted to allow for the notification to be delivered.
+ *
+ * @throws Exception if the test fails
+ */
+ @SmackIntegrationTest
+ public void testNotification() throws Exception {
+ URI uri = new URI("http://www.yesworld.com/lyrics/Fragile.html#9");
+ UserTuneElement.Builder builder = UserTuneElement.getBuilder();
+ UserTuneElement data = builder.setArtist("Yes")
+ .setLength(686)
+ .setRating(8)
+ .setSource("Yessongs")
+ .setTitle("Heart of the Sunrise")
+ .setTrack("3")
+ .setUri(uri)
+ .build();
+
+ IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
+
+ final SimpleResultSyncPoint userTuneReceived = new SimpleResultSyncPoint();
+
+ final PepEventListener userTuneListener = (jid, userTune, id, message) -> {
+ if (userTune.equals(data)) {
+ userTuneReceived.signal();
+ }
+ };
+
+ try {
+ // Register ConTwo's interest in receiving user tune notifications, and wait for that interest to have been propagated.
+ registerListenerAndWait(utm2, ServiceDiscoveryManager.getInstanceFor(conTwo), userTuneListener);
+
+ // Publish the data.
+ utm1.publishUserTune(data); // for the purpose of this test, this needs not be blocking/use publishAndWait();
+
+ // Wait for the data to be received.
+ Object result = userTuneReceived.waitForResult(timeout);
+
+ // Explicitly assert the success case.
+ Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
+ } finally {
+ unregisterListener(utm2, userTuneListener);
+ }
+ }
+
+ /**
+ * Verifies that a notification for a previously sent publication is received as soon as notification filtering
+ * has been adjusted to allow for the notification to be delivered.
+ *
+ * @throws Exception if the test fails
+ */
+ @SmackIntegrationTest
+ public void testNotificationAfterFilterChange() throws Exception {
+ URI uri = new URI("http://www.yesworld.com/lyrics/Fragile.html#8");
+ UserTuneElement.Builder builder = UserTuneElement.getBuilder();
+ UserTuneElement data = builder.setArtist("No")
+ .setLength(306)
+ .setRating(3)
+ .setSource("NoSongs")
+ .setTitle("Sunrise of the Heart")
+ .setTrack("2")
+ .setUri(uri)
+ .build();
+
+ IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout);
+
+ final SimpleResultSyncPoint userTuneReceived = new SimpleResultSyncPoint();
+
+ final PepEventListener userTuneListener = (jid, userTune, id, message) -> {
+ if (userTune.equals(data)) {
+ userTuneReceived.signal();
+ }
+ };
+
+ // TODO Ensure that pre-existing filtering notification excludes userTune.
+ try {
+ // Publish the data
+ publishAndWait(utm1, ServiceDiscoveryManager.getInstanceFor(conOne), data);
+
+ // Adds listener, which implicitly publishes a disco/info filter for userTune notification.
+ registerListenerAndWait(utm2, ServiceDiscoveryManager.getInstanceFor(conTwo), userTuneListener);
+
+ // Wait for the data to be received.
+ try {
+ Object result = userTuneReceived.waitForResult(timeout);
+
+ // Explicitly assert the success case.
+ Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not.");
+ } catch (TimeoutException e) {
+ Assertions.fail("Expected to receive a PEP notification, but did not.");
+ }
+ } finally {
+ unregisterListener(utm2, userTuneListener);
+ }
+ }
+
+ /**
+ * Registers a listener for User Tune data. This implicitly publishes a CAPS update to include a notification
+ * filter for the usertune node. This method blocks until the server has indicated that this update has been
+ * received.
+ *
+ * @param userTuneManager The UserTuneManager instance for the connection that is expected to receive data.
+ * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
+ * @param listener A listener instance for UserTune data that is to be registered.
+ *
+ * @throws Exception if the test fails
+ */
+ public void registerListenerAndWait(UserTuneManager userTuneManager, ServiceDiscoveryManager discoManager, PepEventListener listener) throws Exception {
+ final SimpleResultSyncPoint notificationFilterReceived = new SimpleResultSyncPoint();
+ final EntityCapabilitiesChangedListener notificationFilterReceivedListener = info -> {
+ if (info.containsFeature(UserTuneManager.USERTUNE_NODE + "+notify")) {
+ notificationFilterReceived.signal();
+ }
+ };
+
+ discoManager.addEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
+ try {
+ userTuneManager.addUserTuneListener(listener);
+ notificationFilterReceived.waitForResult(timeout);
+ } finally {
+ discoManager.removeEntityCapabilitiesChangedListener(notificationFilterReceivedListener);
+ }
+ }
+
+ /**
+ * The functionally reverse of {@link #registerListenerAndWait(UserTuneManager, ServiceDiscoveryManager, PepEventListener)}
+ * with the difference of not being a blocking operation.
+ *
+ * @param userTuneManager The UserTuneManager instance for the connection that was expected to receive data.
+ * @param listener A listener instance for UserTune data that is to be removed.
+ */
+ public void unregisterListener(UserTuneManager userTuneManager, PepEventListener listener) {
+ // Does it make sense to have a method implementation that's one line? This is provided to allow for symmetry in the API.
+ userTuneManager.removeUserTuneListener(listener);
+ }
+
+ /**
+ * Publish data using PEP, and block until the server has echoed the publication back to the publishing user.
+ *
+ * @param userTuneManager The UserTuneManager instance for the connection that is expected to publish data.
+ * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data.
+ * @param data The data to be published.
+ *
+ * @throws Exception if the test fails
+ */
+ public void publishAndWait(UserTuneManager userTuneManager, ServiceDiscoveryManager discoManager, UserTuneElement data) throws Exception {
+ final SimpleResultSyncPoint publicationEchoReceived = new SimpleResultSyncPoint();
+ final PepEventListener publicationEchoListener = (jid, userTune, id, message) -> {
+ if (userTune.equals(data)) {
+ publicationEchoReceived.signal();
+ }
+ };
+ try {
+ registerListenerAndWait(userTuneManager, discoManager, publicationEchoListener);
+ userTuneManager.addUserTuneListener(publicationEchoListener);
+ userTuneManager.publishUserTune(data);
+ } finally {
+ userTuneManager.removeUserTuneListener(publicationEchoListener);
+ }
+ }
}
diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java
index 65e004de2..712c4fb8c 100644
--- a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java
+++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java
@@ -118,7 +118,7 @@ public class RoomInvitation implements ExtensionElement {
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
- XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this));
+ XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this, enclosingNamespace));
xml.closeElement(this);
return xml;
}
diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java
index 2d6d95c58..726af6f65 100644
--- a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java
+++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java
@@ -113,7 +113,7 @@ public class RoomTransfer implements ExtensionElement {
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
- XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this));
+ XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this, enclosingNamespace));
xml.closeElement(this);
return xml;
}
diff --git a/smack-openpgp/build.gradle b/smack-openpgp/build.gradle
index 08d104afa..81c6accbd 100644
--- a/smack-openpgp/build.gradle
+++ b/smack-openpgp/build.gradle
@@ -8,7 +8,7 @@ dependencies {
api project(':smack-extensions')
api project(':smack-experimental')
- api 'org.pgpainless:pgpainless-core:0.2.0'
+ api 'org.pgpainless:pgpainless-core:1.0.0-rc6'
testImplementation "org.bouncycastle:bcprov-jdk15on:${bouncyCastleVersion}"
diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java
index e8b99e3ef..2fa3bbe77 100644
--- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java
+++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java
@@ -29,7 +29,6 @@ import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.stringencoder.Base64;
-
import org.jivesoftware.smackx.ox.OpenPgpContact;
import org.jivesoftware.smackx.ox.OpenPgpMessage;
import org.jivesoftware.smackx.ox.OpenPgpSelf;
@@ -47,6 +46,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.util.io.Streams;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.DocumentSignatureType;
+import org.pgpainless.decryption_verification.ConsumerOptions;
import org.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.decryption_verification.MissingPublicKeyCallback;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
@@ -89,7 +89,7 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider {
SigningOptions signOpts = new SigningOptions();
signOpts.addInlineSignature(getStore().getKeyRingProtector(), self.getSigningKeyRing(),
- "xmpp:" + self.getJid().toString(), DocumentSignatureType.BINARY_DOCUMENT);
+ DocumentSignatureType.BINARY_DOCUMENT);
EncryptionStream cipherStream = PGPainless.encryptAndOrSign()
.onOutputStream(cipherText)
@@ -209,10 +209,10 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider {
DecryptionStream cipherStream = PGPainless.decryptAndOrVerify()
.onInputStream(cipherText)
- .decryptWith(getStore().getKeyRingProtector(), self.getSecretKeys())
- .verifyWith(announcedPublicKeys)
- .handleMissingPublicKeysWith(missingPublicKeyCallback)
- .build();
+ .withOptions(new ConsumerOptions()
+ .addDecryptionKeys(self.getSecretKeys(), getStore().getKeyRingProtector())
+ .addVerificationCerts(announcedPublicKeys)
+ .setMissingCertificateCallback(missingPublicKeyCallback));
Streams.pipeAll(cipherStream, plainText);
diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java
index 3c8483dfe..b1ad533fb 100644
--- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java
+++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java
@@ -37,6 +37,7 @@ import org.bouncycastle.util.io.Streams;
import org.jxmpp.jid.BareJid;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
+import org.pgpainless.decryption_verification.ConsumerOptions;
import org.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.encryption_signing.EncryptionOptions;
import org.pgpainless.encryption_signing.EncryptionStream;
@@ -153,9 +154,8 @@ public class SecretKeyBackupHelper {
try {
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(encryptedIn)
- .decryptWith(Passphrase.fromPassword(backupCode.toString()))
- .doNotVerify()
- .build();
+ .withOptions(new ConsumerOptions()
+ .addDecryptionPassphrase(Passphrase.fromPassword(backupCode.toString())));
Streams.pipeAll(decryptionStream, plaintextOut);
decryptionStream.close();
diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PainlessOpenPgpProviderTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PainlessOpenPgpProviderTest.java
index f6b78493a..5f1210367 100644
--- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PainlessOpenPgpProviderTest.java
+++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PainlessOpenPgpProviderTest.java
@@ -47,29 +47,27 @@ import org.jivesoftware.smackx.ox.store.filebased.FileBasedOpenPgpStore;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.JidTestUtil;
+import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.protection.UnprotectedKeysProtector;
import org.pgpainless.key.util.KeyRingUtils;
public class PainlessOpenPgpProviderTest extends SmackTestSuite {
- private static final File storagePath;
+ private static File storagePath;
private static final BareJid alice = JidTestUtil.BARE_JID_1;
private static final BareJid bob = JidTestUtil.BARE_JID_2;
- static {
+ @BeforeEach
+ @AfterEach
+ public void deletePath() throws IOException {
storagePath = new File(org.apache.commons.io.FileUtils.getTempDirectory(), "smack-painlessprovidertest");
- }
-
- @BeforeClass
- @AfterClass
- public static void deletePath() throws IOException {
org.apache.commons.io.FileUtils.deleteDirectory(storagePath);
}
@@ -142,7 +140,7 @@ public class PainlessOpenPgpProviderTest extends SmackTestSuite {
// Decrypt and Verify
decrypted = bobProvider.decryptAndOrVerify(bobConnection, encrypted.getElement(), bobSelf, aliceForBob);
- OpenPgpV4Fingerprint decryptionFingerprint = decrypted.getMetadata().getDecryptionFingerprint();
+ OpenPgpFingerprint decryptionFingerprint = decrypted.getMetadata().getDecryptionKey().getFingerprint();
assertTrue(bobSelf.getSecretKeys().contains(decryptionFingerprint.getKeyId()));
assertTrue(decrypted.getMetadata().containsVerifiedSignatureFrom(alicePubKeys));
@@ -162,9 +160,9 @@ public class PainlessOpenPgpProviderTest extends SmackTestSuite {
decrypted = bobProvider.decryptAndOrVerify(bobConnection, encrypted.getElement(), bobSelf, aliceForBob);
- decryptionFingerprint = decrypted.getMetadata().getDecryptionFingerprint();
+ decryptionFingerprint = decrypted.getMetadata().getDecryptionKey().getFingerprint();
assertTrue(bobSelf.getSecretKeys().contains(decryptionFingerprint.getKeyId()));
- assertTrue(decrypted.getMetadata().getVerifiedSignatureKeyFingerprints().isEmpty());
+ assertTrue(decrypted.getMetadata().getVerifiedSignatures().isEmpty());
assertEquals(OpenPgpMessage.State.crypt, decrypted.getState());
CryptElement decryptedCrypt = (CryptElement) decrypted.getOpenPgpContentElement();
@@ -182,7 +180,7 @@ public class PainlessOpenPgpProviderTest extends SmackTestSuite {
decrypted = bobProvider.decryptAndOrVerify(bobConnection, encrypted.getElement(), bobSelf, aliceForBob);
- assertNull(decrypted.getMetadata().getDecryptionFingerprint());
+ assertNull(decrypted.getMetadata().getDecryptionKey());
assertTrue(decrypted.getMetadata().containsVerifiedSignatureFrom(alicePubKeys));
assertEquals(OpenPgpMessage.State.sign, decrypted.getState());
diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java
index 13363cd58..7854aedce 100644
--- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java
+++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java
@@ -155,7 +155,7 @@ public class OXInstantMessagingManagerTest extends SmackTestSuite {
assertTrue(metadata.isSigned() && metadata.isEncrypted());
// Check, if one of Bobs keys was used for decryption
- assertNotNull(bobSelf.getSigningKeyRing().getPublicKey(metadata.getDecryptionFingerprint().getKeyId()));
+ assertNotNull(bobSelf.getSigningKeyRing().getPublicKey(metadata.getDecryptionKey().getKeyId()));
// TODO: I observed this assertTrue() to fail sporadically. As a first attempt to diagnose this, a message was
// added to the assertion. However since most (all?) objects used in the message do not implement a proper
diff --git a/smack-openpgp/src/test/resources/logback-test.xml b/smack-openpgp/src/test/resources/logback-test.xml
new file mode 100644
index 000000000..c77b1b2f7
--- /dev/null
+++ b/smack-openpgp/src/test/resources/logback-test.xml
@@ -0,0 +1,21 @@
+
+
+
+ System.out
+
+ %blue(%-5level) %green(%logger{35}) - %msg %n
+
+
+
+
+ System.err
+
+ %blue(%-5level) %green(%logger{35}) - %msg %n
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLGSSAPIMechanism.java b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLGSSAPIMechanism.java
index 210e94ab9..bdef7d673 100644
--- a/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLGSSAPIMechanism.java
+++ b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLGSSAPIMechanism.java
@@ -66,4 +66,8 @@ public class SASLGSSAPIMechanism extends SASLJavaXMechanism {
return new SASLGSSAPIMechanism();
}
+ @Override
+ public boolean requiresPassword() {
+ return false;
+ }
}
diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
index c0549895a..1403a78df 100644
--- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
+++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java
@@ -48,6 +48,7 @@ import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
@@ -712,9 +713,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
SmackTlsContext smackTlsContext = getSmackTlsContext();
Socket plain = socket;
+ int port = plain.getPort();
+ String xmppServiceDomainString = config.getXMPPServiceDomain().toString();
+ SSLSocketFactory sslSocketFactory = smackTlsContext.sslContext.getSocketFactory();
// Secure the plain connection
- socket = smackTlsContext.sslContext.getSocketFactory().createSocket(plain,
- config.getXMPPServiceDomain().toString(), plain.getPort(), true);
+ socket = sslSocketFactory.createSocket(plain, xmppServiceDomainString, port, true);
final SSLSocket sslSocket = (SSLSocket) socket;
// Immediately set the enabled SSL protocols and ciphers. See SMACK-712 why this is
diff --git a/smack-xmlparser-stax/build.gradle b/smack-xmlparser-stax/build.gradle
index 66cf94388..b25ce2e9a 100644
--- a/smack-xmlparser-stax/build.gradle
+++ b/smack-xmlparser-stax/build.gradle
@@ -1,3 +1,8 @@
+// Note that this is also declared in the main build.gradle for
+// subprojects, but since evaluationDependsOnChildren is enabled we
+// need to declare it here too to have bundle{bnd{...}} available
+apply plugin: 'biz.aQute.bnd.builder'
+
description = """\
Smack XML parser using Stax."""
@@ -5,3 +10,13 @@ dependencies {
api project(':smack-xmlparser')
//testCompile project(path: ":smack-xmlparser", configuration: "testRuntime")
}
+
+jar {
+ bundle {
+ bnd(
+ // see http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.loader.html
+ 'Require-Capability': 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)"',
+ 'Provide-Capability': "osgi.serviceloader;osgi.serviceloader=org.jivesoftware.smack.xml.XmlPullParserFactory;register:=org.jivesoftware.smack.xml.stax.StaxXmlPullParserFactory",
+ )
+ }
+}
diff --git a/smack-xmlparser-xpp3/build.gradle b/smack-xmlparser-xpp3/build.gradle
index a0afd7c4a..f0a9f56c6 100644
--- a/smack-xmlparser-xpp3/build.gradle
+++ b/smack-xmlparser-xpp3/build.gradle
@@ -1,3 +1,8 @@
+// Note that this is also declared in the main build.gradle for
+// subprojects, but since evaluationDependsOnChildren is enabled we
+// need to declare it here too to have bundle{bnd{...}} available
+apply plugin: 'biz.aQute.bnd.builder'
+
description = """\
Smack XML parser using XPP3."""
@@ -11,3 +16,13 @@ dependencies {
api project(':smack-xmlparser')
//testCompile project(path: ":smack-xmlparser", configuration: "testRuntime")
}
+
+jar {
+ bundle {
+ bnd(
+ // see http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.loader.html
+ 'Require-Capability': 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)"',
+ 'Provide-Capability': "osgi.serviceloader;osgi.serviceloader=org.jivesoftware.smack.xml.XmlPullParserFactory;register:=org.jivesoftware.smack.xml.xpp3.Xpp3XmlPullParserFactory",
+ )
+ }
+}
diff --git a/smack-xmlparser/build.gradle b/smack-xmlparser/build.gradle
index 46318a504..513a2b153 100644
--- a/smack-xmlparser/build.gradle
+++ b/smack-xmlparser/build.gradle
@@ -1,2 +1,16 @@
+// Note that this is also declared in the main build.gradle for
+// subprojects, but since evaluationDependsOnChildren is enabled we
+// need to declare it here too to have bundle{bnd{...}} available
+apply plugin: 'biz.aQute.bnd.builder'
+
description = """\
Smack XML parser fundamentals"""
+
+jar {
+ bundle {
+ bnd(
+ // see http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.loader.html
+ 'Require-Capability': 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.processor)"',
+ )
+ }
+}
diff --git a/version b/version
index 07b8ab2a4..891d39303 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-4.5.0-alpha1-SNAPSHOT
+4.5.0-alpha2-SNAPSHOT