mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-22 20:12:07 +01:00
Compare commits
133 commits
b9be45ae2c
...
01cd0507b6
Author | SHA1 | Date | |
---|---|---|---|
|
01cd0507b6 | ||
|
ea601d0f9f | ||
|
f07c8919cd | ||
|
cccf3ce5f3 | ||
|
229a6edb1b | ||
|
a15110694a | ||
|
4d026d8ae8 | ||
|
3885153ea1 | ||
|
ba2e36dbc3 | ||
|
4622d00d9e | ||
|
cd70e7aa5a | ||
|
aafc24a966 | ||
|
91a1825eb8 | ||
|
1b0e19f699 | ||
|
4a83d2957e | ||
|
4e715a35e5 | ||
|
67a5c3a41a | ||
|
ad9867ebdf | ||
|
5cd7a6c60e | ||
|
e25be8fea4 | ||
|
3ff553549a | ||
|
4cdb4acf26 | ||
|
7fad14ac0c | ||
|
c06cf72337 | ||
|
0e637068e6 | ||
|
1dae0c0c32 | ||
|
1a727c152e | ||
|
5f75d141ff | ||
|
5c46451cd2 | ||
|
940d7bf02a | ||
|
45a4987818 | ||
|
ae2394ea83 | ||
|
8be1568a5d | ||
|
178e82a3aa | ||
|
817dc0ed3a | ||
|
b3637101ce | ||
|
e530db2e6d | ||
|
56ca31b156 | ||
|
56c66f17d8 | ||
|
e51cf47b29 | ||
|
993a307222 | ||
|
182d01a4b0 | ||
|
e1845a52ca | ||
|
89ef46525e | ||
|
fc7fc10c69 | ||
|
e39adff40f | ||
|
d1273532ce | ||
|
10a2687ff1 | ||
|
69a55aaa84 | ||
|
def7c91e6a | ||
|
4e2d0035ac | ||
|
400a2b2564 | ||
|
33e8053258 | ||
|
819ed87a17 | ||
|
e3c50aeeb7 | ||
|
11cc2d8d77 | ||
|
cad63bc107 | ||
|
8736e3e434 | ||
|
61d56aa06a | ||
|
fdffd58d25 | ||
|
f611941dcf | ||
|
f2c2f45f18 | ||
|
7dc8cc176e | ||
|
2628dc328c | ||
|
403890d988 | ||
|
5731e61782 | ||
|
22cf7bace8 | ||
|
18b25902c4 | ||
53d385ab93 | |||
|
fa88f78232 | ||
|
22b2efc6a6 | ||
|
7fd300888e | ||
|
4ae3fbb073 | ||
|
447c1304cf | ||
|
d8ce2d335b | ||
|
8c87daa7b4 | ||
|
d0be6a6216 | ||
|
6d0a0ff2f4 | ||
|
f03f2c75f8 | ||
|
19270a2eca | ||
|
bd528d2c32 | ||
|
1956048811 | ||
|
e842195b71 | ||
|
ab92bc4b40 | ||
|
0fa6d88575 | ||
|
a0f97707d8 | ||
|
7199003f98 | ||
|
9fb7d6d97c | ||
|
ad6e285346 | ||
|
dac8b728b4 | ||
|
2ff53abef3 | ||
|
d25bd811dc | ||
|
6f67553fcf | ||
|
453ca6aeb0 | ||
|
a3840659aa | ||
|
b243a40e26 | ||
|
585bcb4dc8 | ||
|
27ff37fa98 | ||
|
b675aac81b | ||
|
8074ddd60a | ||
|
9b339efbc1 | ||
|
820adf8865 | ||
|
105c74b22b | ||
|
52a49769f9 | ||
|
ec456399b5 | ||
|
ae4ff244a3 | ||
|
49ad8c0954 | ||
|
b57cf8375f | ||
|
09710b3203 | ||
|
55299fb7e7 | ||
|
8ae5ef1f51 | ||
|
1564b98d60 | ||
|
f0a0796d33 | ||
|
bc281d84a7 | ||
|
5b857a1b82 | ||
|
3f2418dec7 | ||
|
c13f4ccac3 | ||
|
613f1afcab | ||
|
001985647a | ||
|
0d73c21945 | ||
|
32a38c4e77 | ||
|
a0b9279441 | ||
|
48c057ab10 | ||
|
98d530819f | ||
9e33fc56e1 | |||
9c5b0a2a16 | |||
|
f39e433121 | ||
|
d7c708b167 | ||
|
8f760eaeb3 | ||
|
e626580f68 | ||
|
58774ad05b | ||
|
00249f3a67 | ||
|
5e203086b3 |
118 changed files with 4989 additions and 2402 deletions
1855
CHANGELOG.md
Normal file
1855
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load diff
4
NOTICE
4
NOTICE
|
@ -29,6 +29,7 @@ Chris Deering
|
|||
Christoph Fiehe
|
||||
Craig Hesling
|
||||
Damian Minkov
|
||||
Dan Caseley
|
||||
Daniele Ricci
|
||||
Daniel Henninger
|
||||
Daniel Hintze
|
||||
|
@ -44,6 +45,7 @@ Fernando Ramirez
|
|||
Florian Kimmann
|
||||
Florian Schmaus
|
||||
Francisco Vives
|
||||
Frank Matheron
|
||||
Gaston Dombiak
|
||||
Georg Lukas
|
||||
Gilles Cornu
|
||||
|
@ -64,6 +66,7 @@ Jay Kline
|
|||
Jeff Williams
|
||||
Jesus Fuentes
|
||||
John Haubrich
|
||||
Jonathan Lennox
|
||||
Júlio Cesar Bueno Cotta
|
||||
Lars Noschinski
|
||||
Luca Stucchi
|
||||
|
@ -82,6 +85,7 @@ Pete Matern
|
|||
Piotr Nosek
|
||||
Rajat Kumar Gupta
|
||||
Robin Collier
|
||||
Simon Abykov
|
||||
Simon Schuster
|
||||
Son Goku
|
||||
Tairs Rzajevs
|
||||
|
|
14
build.gradle
14
build.gradle
|
@ -6,6 +6,7 @@ buildscript {
|
|||
}
|
||||
dependencies {
|
||||
classpath 'org.kordamp.gradle:clirr-gradle-plugin:0.2.2'
|
||||
classpath "biz.aQute.bnd:biz.aQute.bnd.gradle:6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,7 +148,7 @@ allprojects {
|
|||
smackMinAndroidSdk = 19
|
||||
junitVersion = '5.7.1'
|
||||
commonsIoVersion = '2.6'
|
||||
bouncyCastleVersion = '1.68'
|
||||
bouncyCastleVersion = '1.69'
|
||||
guavaVersion = '30.1-jre'
|
||||
mockitoVersion = '3.7.7'
|
||||
orgReflectionsVersion = '0.9.11'
|
||||
|
@ -196,10 +197,6 @@ allprojects {
|
|||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
// Add OSS Sonatype Snapshot repository
|
||||
maven {
|
||||
url 'https://oss.sonatype.org/content/repositories/snapshots'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
|
@ -450,6 +447,7 @@ subprojects {
|
|||
apply plugin: 'signing'
|
||||
apply plugin: 'checkstyle'
|
||||
apply plugin: 'org.kordamp.gradle.clirr'
|
||||
apply plugin: 'biz.aQute.bnd.builder'
|
||||
|
||||
checkstyle {
|
||||
toolVersion = '8.27'
|
||||
|
@ -615,6 +613,12 @@ subprojects*.jar {
|
|||
manifest {
|
||||
from sharedManifest
|
||||
}
|
||||
bundle {
|
||||
bnd(
|
||||
'-removeheaders': 'Tool, Bnd-*',
|
||||
'-exportcontents': '*',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
configure(subprojects - gplLicensedProjects) {
|
||||
|
|
|
@ -86,6 +86,27 @@ debugger=console
|
|||
|
||||
The framework will first load the properties file from `~/.config/smack-integration-test/properties`
|
||||
|
||||
### Running selected tests only
|
||||
|
||||
Using `enabledTests` is is possible to run only selected tests. The
|
||||
tests can be selected on a per class base or by specifying concrete
|
||||
test methods. In the latter case, the methods must be qualified by a
|
||||
(simple) class name.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ gradle integrationTest -Dsinttest.enabledTests=SoftwareInfoIntegrationTest.test
|
||||
```
|
||||
|
||||
will only run the `test()` method of `SoftwareInfoIntegrationTest`, whereas
|
||||
|
||||
```bash
|
||||
$ gradle integrationTest -Dsinttest.enabledTests=SoftwareInfoIntegrationTest
|
||||
```
|
||||
|
||||
would run all tests defined in the `SoftwareInfoIntegrationTest` class.
|
||||
|
||||
Overview of the components
|
||||
--------------------------
|
||||
|
||||
|
|
38
resources/generate-notice-file
Executable file
38
resources/generate-notice-file
Executable file
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
SMACK_DIR=$(realpath "${SCRIPT_DIR}/..")
|
||||
|
||||
cd "${SMACK_DIR}"
|
||||
|
||||
TEMPFILE=$(mktemp)
|
||||
|
||||
cleanup() {
|
||||
rm "${TEMPFILE}"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
git shortlog -s |\
|
||||
cut -f2- |\
|
||||
grep -v '(no author)' |\
|
||||
grep '\w \w.*' |\
|
||||
sort \
|
||||
> "${TEMPFILE}"
|
||||
|
||||
readonly NOTICE_FILE="${SMACK_DIR}/NOTICE"
|
||||
|
||||
cat <<EOF > "${NOTICE_FILE}"
|
||||
Smack
|
||||
|
||||
An open-source XMPP library
|
||||
maintained by Florian Schmaus
|
||||
|
||||
https://igniterealtime.org/projects/smack
|
||||
|
||||
|
||||
Authors:
|
||||
|
||||
EOF
|
||||
|
||||
cat "${TEMPFILE}" >> "${NOTICE_FILE}"
|
|
@ -1,7 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
git shortlog -s |\
|
||||
cut -f2- |\
|
||||
grep -v '(no author)' |\
|
||||
grep '\w \w.*' |\
|
||||
sort
|
File diff suppressed because it is too large
Load diff
|
@ -43,6 +43,7 @@ import org.jivesoftware.smack.packet.StanzaError;
|
|||
import org.jivesoftware.smack.util.CloseableUtil;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||
|
||||
import org.igniterealtime.jbosh.AbstractBody;
|
||||
import org.igniterealtime.jbosh.BOSHClient;
|
||||
|
@ -200,6 +201,14 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
+ getHost() + ":" + getPort() + ".";
|
||||
throw new SmackException.SmackMessageException(errorMessage);
|
||||
}
|
||||
|
||||
try {
|
||||
XmlPullParser parser = PacketParserUtils.getParserFor(
|
||||
"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'/>");
|
||||
onStreamOpen(parser);
|
||||
} catch (XmlPullParserException | IOException e) {
|
||||
throw new AssertionError("Failed to setup stream environment", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -511,7 +520,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
parseAndProcessStanza(parser);
|
||||
break;
|
||||
case "features":
|
||||
parseFeatures(parser);
|
||||
parseFeaturesAndNotify(parser);
|
||||
break;
|
||||
case "error":
|
||||
// Some BOSH error isn't stream error.
|
||||
|
|
|
@ -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 core components."""
|
||||
|
||||
|
@ -55,3 +60,11 @@ task createVersionResource(type: CreateFileTask) {
|
|||
}
|
||||
|
||||
compileJava.dependsOn(createVersionResource)
|
||||
|
||||
jar {
|
||||
bundle {
|
||||
bnd(
|
||||
'DynamicImport-Package': '*',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -524,9 +524,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
closingStreamReceived = false;
|
||||
streamId = null;
|
||||
|
||||
// The connection should not be connected nor marked as such prior calling connectInternal().
|
||||
assert !connected;
|
||||
|
||||
try {
|
||||
// Perform the actual connection to the XMPP service
|
||||
connectInternal();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software, 2017-2020 Florian Schmaus.
|
||||
* Copyright 2003-2007 Jive Software, 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.
|
||||
|
@ -32,6 +32,7 @@ import java.security.KeyStoreException;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.Provider;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
@ -189,7 +190,7 @@ public abstract class ConnectionConfiguration {
|
|||
protected ConnectionConfiguration(Builder<?, ?> builder) {
|
||||
try {
|
||||
smackTlsContext = getSmackTlsContext(builder.dnssecMode, builder.sslContextFactory,
|
||||
builder.customX509TrustManager, builder.keystoreType, builder.keystorePath,
|
||||
builder.customX509TrustManager, builder.keyManagers, builder.sslContextSecureRandom, builder.keystoreType, builder.keystorePath,
|
||||
builder.callbackHandler, builder.pkcs11Library);
|
||||
} catch (UnrecoverableKeyException | KeyManagementException | NoSuchAlgorithmException | CertificateException
|
||||
| KeyStoreException | NoSuchProviderException | IOException | NoSuchMethodException
|
||||
|
@ -252,7 +253,7 @@ public abstract class ConnectionConfiguration {
|
|||
}
|
||||
|
||||
private static SmackTlsContext getSmackTlsContext(DnssecMode dnssecMode, SslContextFactory sslContextFactory,
|
||||
X509TrustManager trustManager, String keystoreType, String keystorePath,
|
||||
X509TrustManager trustManager, KeyManager[] keyManagers, SecureRandom secureRandom, String keystoreType, String keystorePath,
|
||||
CallbackHandler callbackHandler, String pkcs11Library) throws NoSuchAlgorithmException,
|
||||
CertificateException, IOException, KeyStoreException, NoSuchProviderException,
|
||||
UnrecoverableKeyException, KeyManagementException, UnsupportedCallbackException,
|
||||
|
@ -266,69 +267,10 @@ public abstract class ConnectionConfiguration {
|
|||
context = SSLContext.getInstance("TLS");
|
||||
}
|
||||
|
||||
KeyStore ks = null;
|
||||
PasswordCallback pcb = null;
|
||||
KeyManager[] kms = null;
|
||||
|
||||
if ("PKCS11".equals(keystoreType)) {
|
||||
Constructor<?> c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class);
|
||||
String pkcs11Config = "name = SmartCard\nlibrary = " + pkcs11Library;
|
||||
ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StandardCharsets.UTF_8));
|
||||
Provider p = (Provider) c.newInstance(config);
|
||||
Security.addProvider(p);
|
||||
ks = KeyStore.getInstance("PKCS11", p);
|
||||
pcb = new PasswordCallback("PKCS11 Password: ", false);
|
||||
callbackHandler.handle(new Callback[] { pcb });
|
||||
ks.load(null, pcb.getPassword());
|
||||
} else if ("Apple".equals(keystoreType)) {
|
||||
ks = KeyStore.getInstance("KeychainStore", "Apple");
|
||||
ks.load(null, null);
|
||||
// pcb = new PasswordCallback("Apple Keychain",false);
|
||||
// pcb.setPassword(null);
|
||||
} else if (keystoreType != null) {
|
||||
ks = KeyStore.getInstance(keystoreType);
|
||||
if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) {
|
||||
pcb = new PasswordCallback("Keystore Password: ", false);
|
||||
callbackHandler.handle(new Callback[] { pcb });
|
||||
ks.load(new FileInputStream(keystorePath), pcb.getPassword());
|
||||
} else {
|
||||
InputStream stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
|
||||
try {
|
||||
// Note that PKCS12 keystores need a password one some Java platforms. Hence we try the famous
|
||||
// 'changeit' here. See https://bugs.openjdk.java.net/browse/JDK-8194702
|
||||
char[] password = "changeit".toCharArray();
|
||||
try {
|
||||
ks.load(stream, password);
|
||||
} finally {
|
||||
CloseableUtil.maybeClose(stream);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.FINE, "KeyStore load() threw, attempting 'jks' fallback", e);
|
||||
|
||||
ks = KeyStore.getInstance("jks");
|
||||
// Open the stream again, so that we read it from the beginning.
|
||||
stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
|
||||
try {
|
||||
ks.load(stream, null);
|
||||
} finally {
|
||||
CloseableUtil.maybeClose(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ks != null) {
|
||||
String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
|
||||
if (kmf != null) {
|
||||
if (pcb == null) {
|
||||
kmf.init(ks, null);
|
||||
} else {
|
||||
kmf.init(ks, pcb.getPassword());
|
||||
pcb.clearPassword();
|
||||
}
|
||||
kms = kmf.getKeyManagers();
|
||||
}
|
||||
// TODO: Remove the block below once we removed setKeystorePath(), setKeystoreType(), setCallbackHanlder() and
|
||||
// setPKCS11Library() in the builder, and all related fields and the parameters of this function.
|
||||
if (keyManagers == null) {
|
||||
keyManagers = Builder.getKeyManagersFrom(keystoreType, keystorePath, callbackHandler, pkcs11Library);
|
||||
}
|
||||
|
||||
SmackDaneVerifier daneVerifier = null;
|
||||
|
@ -343,7 +285,7 @@ public abstract class ConnectionConfiguration {
|
|||
}
|
||||
|
||||
// User requested DANE verification.
|
||||
daneVerifier.init(context, kms, trustManager, null);
|
||||
daneVerifier.init(context, keyManagers, trustManager, secureRandom);
|
||||
} else {
|
||||
final TrustManager[] trustManagers;
|
||||
if (trustManager != null) {
|
||||
|
@ -354,7 +296,7 @@ public abstract class ConnectionConfiguration {
|
|||
trustManagers = null;
|
||||
}
|
||||
|
||||
context.init(kms, trustManagers, null);
|
||||
context.init(keyManagers, trustManagers, secureRandom);
|
||||
}
|
||||
|
||||
return new SmackTlsContext(context, daneVerifier);
|
||||
|
@ -405,7 +347,7 @@ public abstract class ConnectionConfiguration {
|
|||
|
||||
/**
|
||||
* Returns the TLS security mode used when making the connection. By default,
|
||||
* the mode is {@link SecurityMode#ifpossible}.
|
||||
* the mode is {@link SecurityMode#required}.
|
||||
*
|
||||
* @return the security mode.
|
||||
*/
|
||||
|
@ -688,6 +630,8 @@ public abstract class ConnectionConfiguration {
|
|||
public abstract static class Builder<B extends Builder<B, C>, C extends ConnectionConfiguration> {
|
||||
private SecurityMode securityMode = SecurityMode.required;
|
||||
private DnssecMode dnssecMode = DnssecMode.disabled;
|
||||
private KeyManager[] keyManagers;
|
||||
private SecureRandom sslContextSecureRandom;
|
||||
private String keystorePath;
|
||||
private String keystoreType;
|
||||
private String pkcs11Library = "pkcs11.config";
|
||||
|
@ -942,7 +886,12 @@ public abstract class ConnectionConfiguration {
|
|||
* @param callbackHandler to obtain information, such as the password or
|
||||
* principal information during the SASL authentication.
|
||||
* @return a reference to this builder.
|
||||
* @deprecated set a callback-handler aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or
|
||||
* {@link #setKeyManagers(KeyManager[])}, created by
|
||||
* {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead.
|
||||
*/
|
||||
// TODO: Remove in Smack 4.6.
|
||||
@Deprecated
|
||||
public B setCallbackHandler(CallbackHandler callbackHandler) {
|
||||
this.callbackHandler = callbackHandler;
|
||||
return getThis();
|
||||
|
@ -960,7 +909,7 @@ public abstract class ConnectionConfiguration {
|
|||
|
||||
/**
|
||||
* Sets the TLS security mode used when making the connection. By default,
|
||||
* the mode is {@link SecurityMode#ifpossible}.
|
||||
* the mode is {@link SecurityMode#required}.
|
||||
*
|
||||
* @param securityMode the security mode.
|
||||
* @return a reference to this builder.
|
||||
|
@ -970,6 +919,47 @@ public abstract class ConnectionConfiguration {
|
|||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link KeyManager}s to initialize the {@link SSLContext} used by Smack to establish the XMPP connection.
|
||||
*
|
||||
* @param keyManagers an array of {@link KeyManager}s to initialize the {@link SSLContext} with.
|
||||
* @return a reference to this builder.
|
||||
* @since 4.4.5
|
||||
*/
|
||||
public B setKeyManagers(KeyManager[] keyManagers) {
|
||||
this.keyManagers = keyManagers;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link KeyManager}s to initialize the {@link SSLContext} used by Smack to establish the XMPP connection.
|
||||
*
|
||||
* @param keyManager the {@link KeyManager}s to initialize the {@link SSLContext} with.
|
||||
* @return a reference to this builder.
|
||||
* @see #setKeyManagers(KeyManager[])
|
||||
* @since 4.4.5
|
||||
*/
|
||||
public B setKeyManager(KeyManager keyManager) {
|
||||
KeyManager[] keyManagers = new KeyManager[] { keyManager };
|
||||
return setKeyManagers(keyManagers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link SecureRandom} used to initialize the {@link SSLContext} used by Smack to establish the XMPP
|
||||
* connection. Note that you usually do not need (nor want) to set this. Because if the {@link SecureRandom} is
|
||||
* not explicitly set, Smack will initialize the {@link SSLContext} with <code>null</code> as
|
||||
* {@link SecureRandom} argument. And all sane {@link SSLContext} implementations will then select a safe secure
|
||||
* random source by default.
|
||||
*
|
||||
* @param secureRandom the {@link SecureRandom} to initialize the {@link SSLContext} with.
|
||||
* @return a reference to this builder.
|
||||
* @since 4.4.5
|
||||
*/
|
||||
public B setSslContextSecureRandom(SecureRandom secureRandom) {
|
||||
this.sslContextSecureRandom = secureRandom;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the keystore file. The key store file contains the
|
||||
* certificates that may be used to authenticate the client to the server,
|
||||
|
@ -977,7 +967,12 @@ public abstract class ConnectionConfiguration {
|
|||
*
|
||||
* @param keystorePath the path to the keystore file.
|
||||
* @return a reference to this builder.
|
||||
* @deprecated set a keystore-path aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or
|
||||
* {@link #setKeyManagers(KeyManager[])}, created by
|
||||
* {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead.
|
||||
*/
|
||||
// TODO: Remove in Smack 4.6.
|
||||
@Deprecated
|
||||
public B setKeystorePath(String keystorePath) {
|
||||
this.keystorePath = keystorePath;
|
||||
return getThis();
|
||||
|
@ -988,7 +983,12 @@ public abstract class ConnectionConfiguration {
|
|||
*
|
||||
* @param keystoreType the keystore type.
|
||||
* @return a reference to this builder.
|
||||
* @deprecated set a key-type aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or
|
||||
* {@link #setKeyManagers(KeyManager[])}, created by
|
||||
* {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead.
|
||||
*/
|
||||
// TODO: Remove in Smack 4.6.
|
||||
@Deprecated
|
||||
public B setKeystoreType(String keystoreType) {
|
||||
this.keystoreType = keystoreType;
|
||||
return getThis();
|
||||
|
@ -1000,7 +1000,12 @@ public abstract class ConnectionConfiguration {
|
|||
*
|
||||
* @param pkcs11Library the path to the PKCS11 library file.
|
||||
* @return a reference to this builder.
|
||||
* @deprecated set a PKCS11-library aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or
|
||||
* {@link #setKeyManagers(KeyManager[])}, created by
|
||||
* {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead.
|
||||
*/
|
||||
// TODO: Remove in Smack 4.6.
|
||||
@Deprecated
|
||||
public B setPKCS11Library(String pkcs11Library) {
|
||||
this.pkcs11Library = pkcs11Library;
|
||||
return getThis();
|
||||
|
@ -1276,5 +1281,77 @@ public abstract class ConnectionConfiguration {
|
|||
public abstract C build();
|
||||
|
||||
protected abstract B getThis();
|
||||
|
||||
public static KeyManager[] getKeyManagersFrom(String keystoreType, String keystorePath,
|
||||
CallbackHandler callbackHandler, String pkcs11Library)
|
||||
throws NoSuchMethodException, SecurityException, ClassNotFoundException, KeyStoreException,
|
||||
NoSuchProviderException, NoSuchAlgorithmException, CertificateException, IOException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, UnsupportedCallbackException, UnrecoverableKeyException {
|
||||
KeyManager[] keyManagers = null;
|
||||
KeyStore ks = null;
|
||||
PasswordCallback pcb = null;
|
||||
|
||||
if ("PKCS11".equals(keystoreType)) {
|
||||
Constructor<?> c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class);
|
||||
String pkcs11Config = "name = SmartCard\nlibrary = " + pkcs11Library;
|
||||
ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StandardCharsets.UTF_8));
|
||||
Provider p = (Provider) c.newInstance(config);
|
||||
Security.addProvider(p);
|
||||
ks = KeyStore.getInstance("PKCS11", p);
|
||||
pcb = new PasswordCallback("PKCS11 Password: ", false);
|
||||
callbackHandler.handle(new Callback[] { pcb });
|
||||
ks.load(null, pcb.getPassword());
|
||||
} else if ("Apple".equals(keystoreType)) {
|
||||
ks = KeyStore.getInstance("KeychainStore", "Apple");
|
||||
ks.load(null, null);
|
||||
// pcb = new PasswordCallback("Apple Keychain",false);
|
||||
// pcb.setPassword(null);
|
||||
} else if (keystoreType != null) {
|
||||
ks = KeyStore.getInstance(keystoreType);
|
||||
if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) {
|
||||
pcb = new PasswordCallback("Keystore Password: ", false);
|
||||
callbackHandler.handle(new Callback[] { pcb });
|
||||
ks.load(new FileInputStream(keystorePath), pcb.getPassword());
|
||||
} else {
|
||||
InputStream stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
|
||||
try {
|
||||
// Note that PKCS12 keystores need a password one some Java platforms. Hence we try the famous
|
||||
// 'changeit' here. See https://bugs.openjdk.java.net/browse/JDK-8194702
|
||||
char[] password = "changeit".toCharArray();
|
||||
try {
|
||||
ks.load(stream, password);
|
||||
} finally {
|
||||
CloseableUtil.maybeClose(stream);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.FINE, "KeyStore load() threw, attempting 'jks' fallback", e);
|
||||
|
||||
ks = KeyStore.getInstance("jks");
|
||||
// Open the stream again, so that we read it from the beginning.
|
||||
stream = TLSUtils.getDefaultTruststoreStreamIfPossible();
|
||||
try {
|
||||
ks.load(stream, null);
|
||||
} finally {
|
||||
CloseableUtil.maybeClose(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ks != null) {
|
||||
String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm);
|
||||
if (kmf != null) {
|
||||
if (pcb == null) {
|
||||
kmf.init(ks, null);
|
||||
} else {
|
||||
kmf.init(ks, pcb.getPassword());
|
||||
pcb.clearPassword();
|
||||
}
|
||||
keyManagers = kmf.getKeyManagers();
|
||||
}
|
||||
}
|
||||
|
||||
return keyManagers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,16 +170,20 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
|||
return exception;
|
||||
}
|
||||
|
||||
private boolean callbacksInvoked;
|
||||
|
||||
protected final synchronized void maybeInvokeCallbacks() {
|
||||
if (cancelled) {
|
||||
if (cancelled || callbacksInvoked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((result != null || exception != null) && completionCallback != null) {
|
||||
callbacksInvoked = true;
|
||||
completionCallback.accept(this);
|
||||
}
|
||||
|
||||
if (result != null && successCallback != null) {
|
||||
callbacksInvoked = true;
|
||||
AbstractXMPPConnection.asyncGo(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -188,6 +192,7 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
|||
});
|
||||
}
|
||||
else if (exception != null && exceptionCallback != null) {
|
||||
callbacksInvoked = true;
|
||||
AbstractXMPPConnection.asyncGo(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
|
|
@ -221,12 +221,11 @@ public class SmackReactor {
|
|||
selectWait = 0;
|
||||
} else {
|
||||
selectWait = nextScheduledAction.getTimeToDueMillis();
|
||||
}
|
||||
|
||||
if (selectWait < 0) {
|
||||
if (selectWait <= 0) {
|
||||
// A scheduled action was just released and became ready to execute.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Before we call select, we handle the pending the interest Ops. This will not block since no other
|
||||
// thread is currently in select() at this time.
|
||||
|
|
|
@ -120,6 +120,16 @@ public abstract class XMPPException extends Exception {
|
|||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stanza associated with this exception.
|
||||
*
|
||||
* @return the stanza from which this exception was created or {@code null} if the exception is not from a
|
||||
* stanza.
|
||||
*/
|
||||
public Stanza getStanza() {
|
||||
return stanza;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request which triggered the error response causing this exception.
|
||||
*
|
||||
|
|
|
@ -186,6 +186,10 @@ public class StateDescriptorGraph {
|
|||
|
||||
for (GraphVertex<Class<? extends StateDescriptor>> successor : sortedSuccessors) {
|
||||
GraphVertex<StateDescriptor> successorVertex = successorStateDescriptors.get(successor.element);
|
||||
if (successorVertex == null) {
|
||||
// The successor does not exist, probably because its module was not enabled.
|
||||
continue;
|
||||
}
|
||||
node.addOutgoingEdge(successorVertex);
|
||||
|
||||
// Recurse further.
|
||||
|
|
|
@ -202,7 +202,7 @@ public abstract class IQ extends Stanza implements IqView {
|
|||
|
||||
// Add the query section if there is one.
|
||||
IQChildElementXmlStringBuilder iqChildElement = getIQChildElementBuilder(
|
||||
new IQChildElementXmlStringBuilder(this));
|
||||
new IQChildElementXmlStringBuilder(getChildElementName(), getChildElementNamespace(), null, xml.getXmlEnvironment()));
|
||||
// TOOD: Document the cases where iqChildElement is null but childElementName not. And if there are none, change
|
||||
// the logic.
|
||||
if (iqChildElement == null) {
|
||||
|
@ -399,17 +399,16 @@ public abstract class IQ extends Stanza implements IqView {
|
|||
|
||||
private boolean isEmptyElement;
|
||||
|
||||
private IQChildElementXmlStringBuilder(IQ iq) {
|
||||
this(iq.getChildElementName(), iq.getChildElementNamespace());
|
||||
public IQChildElementXmlStringBuilder(ExtensionElement extensionElement,
|
||||
XmlEnvironment enclosingXmlEnvironment) {
|
||||
this(extensionElement.getElementName(), extensionElement.getNamespace(), extensionElement.getLanguage(),
|
||||
enclosingXmlEnvironment);
|
||||
}
|
||||
|
||||
public IQChildElementXmlStringBuilder(ExtensionElement pe) {
|
||||
this(pe.getElementName(), pe.getNamespace());
|
||||
}
|
||||
|
||||
private IQChildElementXmlStringBuilder(String element, String namespace) {
|
||||
prelude(element, namespace);
|
||||
this.element = element;
|
||||
private IQChildElementXmlStringBuilder(String elementName, String xmlNs, String xmlLang,
|
||||
XmlEnvironment enclosingXmlEnvironment) {
|
||||
super(elementName, xmlNs, xmlLang, enclosingXmlEnvironment);
|
||||
this.element = elementName;
|
||||
}
|
||||
|
||||
public void setEmptyElement() {
|
||||
|
|
|
@ -184,6 +184,19 @@ public abstract class StanzaBuilder<B extends StanzaBuilder<B>> implements Stanz
|
|||
return getThis();
|
||||
}
|
||||
|
||||
public final B removeExtension(String elementName, String namespace) {
|
||||
QName key = new QName(namespace, elementName);
|
||||
extensionElements.remove(key);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
public final B removeExtension(ExtensionElement extension) {
|
||||
QName key = extension.getQName();
|
||||
List<XmlElement> list = extensionElements.getAll(key);
|
||||
list.remove(extension);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
public abstract Stanza build();
|
||||
|
||||
public abstract B getThis();
|
||||
|
|
|
@ -34,7 +34,7 @@ public class ExceptionThrowingCallbackWithHint extends ExceptionThrowingCallback
|
|||
|
||||
@Override
|
||||
public void handleUnparsableStanza(UnparseableStanza packetData) throws IOException {
|
||||
LOGGER.warning("Parsing exception encountered."
|
||||
LOGGER.warning("Parsing exception \"" + packetData.getParsingException().getMessage() + "\" encountered."
|
||||
+ " This exception will be re-thrown, leading to a disconnect."
|
||||
+ " You can change this behavior by setting a different ParsingExceptionCallback using setParsingExceptionCallback()."
|
||||
+ " More information an be found in AbstractXMPPConnection's javadoc.");
|
||||
|
|
|
@ -30,7 +30,7 @@ import org.jivesoftware.smack.xml.XmlPullParserException;
|
|||
|
||||
/**
|
||||
* <p>
|
||||
* <b>Deprecation Notice:</b> This class is deprecated, use {@link IQProvider} instead.
|
||||
* <b>Deprecation Notice:</b> This class is deprecated, use {@link IqProvider} instead.
|
||||
* </p>
|
||||
* An abstract class for parsing custom IQ packets. Each IQProvider must be registered with
|
||||
* the ProviderManager class for it to be used. Every implementation of this
|
||||
|
|
|
@ -60,4 +60,8 @@ public class SASLAnonymous extends SASLMechanism {
|
|||
// SASL Anonymous is always successful :)
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresPassword() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,4 +89,11 @@ public class CollectionUtil {
|
|||
}
|
||||
return Collections.singletonList(element);
|
||||
}
|
||||
|
||||
public static <T> Set<T> nullSafeUnmodifiableSet(Set<T> set) {
|
||||
if (set == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return Collections.unmodifiableSet(set);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
*
|
||||
* 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.smack.util;
|
||||
|
||||
public final class DoOnce {
|
||||
|
||||
private boolean done;
|
||||
|
||||
public void once(Runnable run) {
|
||||
if (done) return;
|
||||
|
||||
done = true;
|
||||
run.run();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software, 2019 Florian Schmaus.
|
||||
* Copyright 2003-2007 Jive Software, 2019-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.
|
||||
|
@ -76,9 +76,9 @@ public class PacketParserUtils {
|
|||
return getParserFor(new StringReader(stanza));
|
||||
}
|
||||
|
||||
public static XmlPullParser getParserFor(InputStream inputStream) throws XmlPullParserException {
|
||||
public static XmlPullParser getParserFor(InputStream inputStream) throws XmlPullParserException, IOException {
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
return SmackXmlParser.newXmlParser(inputStreamReader);
|
||||
return getParserFor(inputStreamReader);
|
||||
}
|
||||
|
||||
public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException {
|
||||
|
@ -501,6 +501,23 @@ public class PacketParserUtils {
|
|||
return parseIQ(parser, null);
|
||||
}
|
||||
|
||||
public static IqData parseIqData(XmlPullParser parser) throws XmppStringprepException {
|
||||
final String id = parser.getAttributeValue("", "id");
|
||||
IqData iqData = StanzaBuilder.buildIqData(id);
|
||||
|
||||
final Jid to = ParserUtils.getJidAttribute(parser, "to");
|
||||
iqData.to(to);
|
||||
|
||||
final Jid from = ParserUtils.getJidAttribute(parser, "from");
|
||||
iqData.from(from);
|
||||
|
||||
String typeString = parser.getAttributeValue("", "type");
|
||||
final IQ.Type type = IQ.Type.fromString(typeString);
|
||||
iqData.ofType(type);
|
||||
|
||||
return iqData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an IQ packet.
|
||||
*
|
||||
|
@ -518,18 +535,7 @@ public class PacketParserUtils {
|
|||
XmlEnvironment iqXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment);
|
||||
IQ iqPacket = null;
|
||||
StanzaError error = null;
|
||||
|
||||
final String id = parser.getAttributeValue("", "id");
|
||||
IqData iqData = StanzaBuilder.buildIqData(id);
|
||||
|
||||
final Jid to = ParserUtils.getJidAttribute(parser, "to");
|
||||
iqData.to(to);
|
||||
|
||||
final Jid from = ParserUtils.getJidAttribute(parser, "from");
|
||||
iqData.from(from);
|
||||
|
||||
final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
|
||||
iqData.ofType(type);
|
||||
IqData iqData = parseIqData(parser);
|
||||
|
||||
outerloop: while (true) {
|
||||
XmlPullParser.Event eventType = parser.next();
|
||||
|
@ -549,8 +555,8 @@ public class PacketParserUtils {
|
|||
if (provider != null) {
|
||||
iqPacket = provider.parse(parser, iqData, outerXmlEnvironment);
|
||||
}
|
||||
// Note that if we reach this code, it is guranteed that the result IQ contained a child element
|
||||
// (RFC 6120 § 8.2.3 6) because otherwhise we would have reached the END_ELEMENT first.
|
||||
// Note that if we reach this code, it is guaranteed that the result IQ contained a child element
|
||||
// (RFC 6120 § 8.2.3 6) because otherwise we would have reached the END_ELEMENT first.
|
||||
else {
|
||||
// No Provider found for the IQ stanza, parse it to an UnparsedIQ instance
|
||||
// so that the content of the IQ can be examined later on
|
||||
|
@ -571,7 +577,7 @@ public class PacketParserUtils {
|
|||
}
|
||||
// Decide what to do when an IQ packet was not understood
|
||||
if (iqPacket == null) {
|
||||
switch (type) {
|
||||
switch (iqData.getType()) {
|
||||
case error:
|
||||
// If an IQ packet wasn't created above, create an empty error IQ packet.
|
||||
iqPacket = new ErrorIQ(error);
|
||||
|
@ -585,10 +591,10 @@ public class PacketParserUtils {
|
|||
}
|
||||
|
||||
// Set basic values on the iq packet.
|
||||
iqPacket.setStanzaId(id);
|
||||
iqPacket.setTo(to);
|
||||
iqPacket.setFrom(from);
|
||||
iqPacket.setType(type);
|
||||
iqPacket.setStanzaId(iqData.getStanzaId());
|
||||
iqPacket.setTo(iqData.getTo());
|
||||
iqPacket.setFrom(iqData.getFrom());
|
||||
iqPacket.setType(iqData.getType());
|
||||
iqPacket.setError(error);
|
||||
|
||||
return iqPacket;
|
||||
|
|
|
@ -78,6 +78,7 @@ public class ParserUtils {
|
|||
throws XmlPullParserException, IOException {
|
||||
XmlPullParser.Event event = parser.getEventType();
|
||||
while (!(event == XmlPullParser.Event.END_ELEMENT && parser.getDepth() == depth)) {
|
||||
assert event != XmlPullParser.Event.END_DOCUMENT;
|
||||
event = parser.next();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software, 2016-2020 Florian Schmaus.
|
||||
* Copyright 2003-2007 Jive Software, 2016-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,6 +20,7 @@ package org.jivesoftware.smack.util;
|
|||
import java.io.IOException;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
@ -605,4 +606,13 @@ public class StringUtils {
|
|||
String[] lines = input.split(PORTABLE_NEWLINE_REGEX);
|
||||
return Arrays.asList(lines);
|
||||
}
|
||||
|
||||
public static List<String> toStrings(Collection<? extends CharSequence> charSequences) {
|
||||
List<String> res = new ArrayList<>(charSequences.size());
|
||||
for (CharSequence cs : charSequences) {
|
||||
String string = cs.toString();
|
||||
res.add(string);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2014-2020 Florian Schmaus
|
||||
* Copyright 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.
|
||||
|
@ -52,11 +52,13 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
|
|||
}
|
||||
|
||||
public XmlStringBuilder(XmlElement element, XmlEnvironment enclosingXmlEnvironment) {
|
||||
sb = new LazyStringBuilder();
|
||||
halfOpenElement(element);
|
||||
this(element.getElementName(), element.getNamespace(), element.getLanguage(), enclosingXmlEnvironment);
|
||||
}
|
||||
|
||||
public XmlStringBuilder(String elementName, String xmlNs, String xmlLang, XmlEnvironment enclosingXmlEnvironment) {
|
||||
sb = new LazyStringBuilder();
|
||||
halfOpenElement(elementName);
|
||||
|
||||
String xmlNs = element.getNamespace();
|
||||
String xmlLang = element.getLanguage();
|
||||
if (enclosingXmlEnvironment == null) {
|
||||
xmlnsAttribute(xmlNs);
|
||||
xmllangAttribute(xmlLang);
|
||||
|
@ -286,8 +288,7 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element {
|
|||
|
||||
public XmlStringBuilder attribute(String name, Enum<?> value) {
|
||||
assert value != null;
|
||||
// TODO: Should use toString() instead of name().
|
||||
attribute(name, value.name());
|
||||
attribute(name, value.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2019-2021 Florian Schmaus
|
||||
* Copyright 2019-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.
|
||||
|
@ -28,9 +28,13 @@ import java.util.function.Predicate;
|
|||
import javax.xml.namespace.QName;
|
||||
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.IqData;
|
||||
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||
import org.jivesoftware.smack.provider.AbstractProvider;
|
||||
import org.jivesoftware.smack.provider.IqProvider;
|
||||
import org.jivesoftware.smack.provider.Provider;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.jivesoftware.smack.util.ParserUtils;
|
||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserFactory;
|
||||
|
@ -58,41 +62,56 @@ public class SmackTestUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static <E extends Element, P extends Provider<E>> E parse(CharSequence xml, Class<P> providerClass, XmlPullParserKind parserKind)
|
||||
public static <E extends Element, P extends AbstractProvider<E>> E parse(CharSequence xml, Class<P> providerClass, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException, SmackParsingException {
|
||||
P provider = providerClassToProvider(providerClass);
|
||||
return parse(xml, provider, parserKind);
|
||||
}
|
||||
|
||||
public static <E extends Element, P extends Provider<E>> E parse(InputStream inputStream, Class<P> providerClass, XmlPullParserKind parserKind)
|
||||
public static <E extends Element, P extends AbstractProvider<E>> E parse(InputStream inputStream, Class<P> providerClass, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException, SmackParsingException {
|
||||
P provider = providerClassToProvider(providerClass);
|
||||
return parse(inputStream, provider, parserKind);
|
||||
}
|
||||
|
||||
public static <E extends Element, P extends Provider<E>> E parse(Reader reader, Class<P> providerClass, XmlPullParserKind parserKind)
|
||||
public static <E extends Element, P extends AbstractProvider<E>> E parse(Reader reader, Class<P> providerClass, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException, SmackParsingException {
|
||||
P provider = providerClassToProvider(providerClass);
|
||||
return parse(reader, provider, parserKind);
|
||||
}
|
||||
|
||||
public static <E extends Element> E parse(CharSequence xml, Provider<E> provider, XmlPullParserKind parserKind)
|
||||
public static <E extends Element> E parse(CharSequence xml, AbstractProvider<E> provider, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException, SmackParsingException {
|
||||
String xmlString = xml.toString();
|
||||
Reader reader = new StringReader(xmlString);
|
||||
return parse(reader, provider, parserKind);
|
||||
}
|
||||
|
||||
public static <E extends Element> E parse(InputStream inputStream, Provider<E> provider, XmlPullParserKind parserKind)
|
||||
public static <E extends Element> E parse(InputStream inputStream, AbstractProvider<E> provider, XmlPullParserKind parserKind)
|
||||
throws XmlPullParserException, IOException, SmackParsingException {
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
return parse(inputStreamReader, provider, parserKind);
|
||||
}
|
||||
|
||||
public static <E extends Element> E parse(Reader reader, Provider<E> provider, XmlPullParserKind parserKind)
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E extends Element> E parse(Reader reader, AbstractProvider<E> 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<E> provider = (Provider<E>) 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 <E extends Element, P extends Provider<E>> P providerClassToProvider(Class<P> providerClass) {
|
||||
private static <E extends Element, P extends AbstractProvider<E>> P providerClassToProvider(Class<P> providerClass) {
|
||||
P provider;
|
||||
|
||||
try {
|
||||
|
|
|
@ -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<String> 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<String> 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<String> buffer, JTextArea jTextArea) {
|
||||
buffer.add(string);
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
List<String> 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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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<Message> 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 <a href="https://xmpp.org/extensions/xep-0313.html#support">XEP-0313 § 7. Determining support</a>
|
||||
*/
|
||||
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<Jid> alwaysJids;
|
||||
private final List<Jid> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Message> forwarded);
|
||||
|
||||
/**
|
||||
* Create a MAM fin IQ class.
|
||||
*
|
||||
* @param queryId id of the query
|
||||
* @param rsmSet the RSM set included in the {@code <fin/>}
|
||||
* @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<Jid> alwaysJids, List<Jid> neverJids, MamPrefsIQ.DefaultBehavior defaultBehavior);
|
||||
|
||||
/**
|
||||
* Construct a new MAM {@code <prefs/>} 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);
|
||||
|
||||
}
|
|
@ -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</a>
|
||||
*
|
||||
*/
|
||||
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<Message> forwarded) {
|
||||
public MamResultExtension(MamVersion version, String queryId, String id, Forwarded<Message> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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 <prefs/>} 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<Jid> alwaysJids, List<Jid> neverJids, DefaultBehavior defaultBehavior) {
|
||||
super(ELEMENT, NAMESPACE);
|
||||
public MamPrefsIQ(MamVersion version, List<Jid> alwaysJids, List<Jid> neverJids, DefaultBehavior defaultBehavior) {
|
||||
super(ELEMENT, version.getNamespace());
|
||||
setType(Type.set);
|
||||
this.alwaysJids = alwaysJids;
|
||||
this.neverJids = neverJids;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<Message> 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<Jid> alwaysJids, List<Jid> 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<Message> forwarded) {
|
||||
super(MamVersion.MAM1, queryId, id, forwarded);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Message> 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<Jid> alwaysJids, List<Jid> 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<Message> forwarded) {
|
||||
super(MamVersion.MAM2, queryId, id, forwarded);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<MamFinIQ> {
|
|||
|
||||
@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<MamFinIQ> {
|
|||
}
|
||||
}
|
||||
|
||||
return new MamFinIQ(queryId, rsmSet, complete, stable);
|
||||
return elementFactory.newFinIQ(queryId, rsmSet, complete, stable);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<MamPrefsIQ> {
|
||||
|
||||
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<Jid> alwaysJids = null;
|
||||
List<Jid> neverJids = null;
|
||||
|
||||
|
@ -82,7 +81,7 @@ public class MamPrefsIQProvider extends IQProvider<MamPrefsIQ> {
|
|||
}
|
||||
}
|
||||
|
||||
return new MamPrefsIQ(alwaysJids, neverJids, defaultBehavior);
|
||||
return elementFactory.newPrefsIQ(alwaysJids, neverJids, defaultBehavior);
|
||||
}
|
||||
|
||||
private static List<Jid> iterateJids(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
|
|
|
@ -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<MamQueryIQ> {
|
|||
@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<MamQueryIQ> {
|
|||
}
|
||||
}
|
||||
|
||||
return new MamQueryIQ(queryId, node, dataForm);
|
||||
return elementFactory.newQueryIQ(queryId, node, dataForm);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<MamResultExtensi
|
|||
@Override
|
||||
public MamResultExtension parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment)
|
||||
throws XmlPullParserException, IOException, SmackParsingException, ParseException {
|
||||
MamElementFactory elementFactory = MamElementFactory.forParser(parser);
|
||||
Forwarded<Message> forwarded = null;
|
||||
String queryId = parser.getAttributeValue("", "queryid");
|
||||
String id = parser.getAttributeValue("", "id");
|
||||
|
@ -69,7 +71,7 @@ public class MamResultProvider extends ExtensionElementProvider<MamResultExtensi
|
|||
}
|
||||
}
|
||||
|
||||
return new MamResultExtension(queryId, id, forwarded);
|
||||
return elementFactory.newResultExtension(queryId, id, forwarded);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,6 +35,26 @@
|
|||
<namespace>urn:xmpp:mam:2</namespace>
|
||||
<className>org.jivesoftware.smackx.mam.provider.MamResultProvider</className>
|
||||
</extensionProvider>
|
||||
<iqProvider>
|
||||
<elementName>prefs</elementName>
|
||||
<namespace>urn:xmpp:mam:1</namespace>
|
||||
<className>org.jivesoftware.smackx.mam.provider.MamPrefsIQProvider</className>
|
||||
</iqProvider>
|
||||
<iqProvider>
|
||||
<elementName>query</elementName>
|
||||
<namespace>urn:xmpp:mam:1</namespace>
|
||||
<className>org.jivesoftware.smackx.mam.provider.MamQueryIQProvider</className>
|
||||
</iqProvider>
|
||||
<iqProvider>
|
||||
<elementName>fin</elementName>
|
||||
<namespace>urn:xmpp:mam:1</namespace>
|
||||
<className>org.jivesoftware.smackx.mam.provider.MamFinIQProvider</className>
|
||||
</iqProvider>
|
||||
<extensionProvider>
|
||||
<elementName>result</elementName>
|
||||
<namespace>urn:xmpp:mam:1</namespace>
|
||||
<className>org.jivesoftware.smackx.mam.provider.MamResultProvider</className>
|
||||
</extensionProvider>
|
||||
|
||||
<!-- XEP-0323: Internet of Things - Data -->
|
||||
<iqProvider>
|
||||
|
|
|
@ -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<String> fieldsNames, List<? extends CharSequence> fieldsValues) {
|
||||
String xml = "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>" + "<value>"
|
||||
+ MamElements.NAMESPACE + "</value>" + "</field>";
|
||||
+ MamVersion.MAM2.getNamespace() + "</value>" + "</field>";
|
||||
|
||||
for (int i = 0; i < fieldsNames.size() && i < fieldsValues.size(); i++) {
|
||||
xml += "<field var='" + fieldsNames.get(i) + "'>" + "<value>" + fieldsValues.get(i) + "</value>"
|
||||
|
@ -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<String> 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<String> 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<String> fields = new ArrayList<>();
|
||||
fields.add("with");
|
||||
|
|
|
@ -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 = "<iq type='set' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:2' default='roster'>"
|
||||
private static final String exampleMamPrefsIQ1 = "<prefs xmlns='urn:xmpp:mam:2' default='roster'>"
|
||||
+ "<always>" + "<jid>romeo@montague.lit</jid>" + "</always>" + "<never>"
|
||||
+ "<jid>montague@montague.lit</jid>" + "</never>" + "</prefs>" + "</iq>";
|
||||
+ "<jid>montague@montague.lit</jid>" + "</never>" + "</prefs>";
|
||||
|
||||
private static final String exampleMamPrefsIQ2 = "<iq type='set' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:2' default='roster'>"
|
||||
private static final String exampleMamPrefsIQ2 = "<prefs xmlns='urn:xmpp:mam:2' default='roster'>"
|
||||
+ "<always>" + "<jid>romeo@montague.lit</jid>" + "<jid>montague@montague.lit</jid>" + "</always>"
|
||||
+ "<never>" + "</never>" + "</prefs>" + "</iq>";
|
||||
+ "<never>" + "</never>" + "</prefs>";
|
||||
|
||||
private static final String exampleMamPrefsIQ3 = "<iq type='get' id='juliet3'>" + "<prefs xmlns='urn:xmpp:mam:2'>" + "</prefs>"
|
||||
+ "</iq>";
|
||||
private static final String exampleMamPrefsIQ3 = "<prefs xmlns='urn:xmpp:mam:2'>" + "</prefs>";
|
||||
|
||||
private static final String exampleMamPrefsResultIQ = "<iq type='result' id='juliet3'>"
|
||||
+ "<prefs xmlns='urn:xmpp:mam:2' default='roster'>" + "<always>" + "<jid>romeo@montague.lit</jid>"
|
||||
+ "</always>" + "<never>" + "<jid>sarasa@montague.lit</jid>" + "<jid>montague@montague.lit</jid>"
|
||||
+ "</never>" + "</prefs>" + "</iq>";
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = "<iq id='sarasa' type='get'>" + "<prefs xmlns='" + MamElements.NAMESPACE
|
||||
private static final String retrievePrefsStanzaExample = "<iq id='sarasa' type='get'>" + "<prefs xmlns='" + MamVersion.MAM2.getNamespace()
|
||||
+ "'/>" + "</iq>";
|
||||
|
||||
private static final String updatePrefsStanzaExample = "<iq id='sarasa' type='set'>" + "<prefs xmlns='" + MamElements.NAMESPACE
|
||||
private static final String updatePrefsStanzaExample = "<iq id='sarasa' type='set'>" + "<prefs xmlns='" + MamVersion.MAM2.getNamespace()
|
||||
+ "' default='roster'>" + "<always>" + "<jid>romeo@montague.lit</jid>" + "<jid>other@montague.lit</jid>"
|
||||
+ "</always>" + "<never>" + "<jid>montague@montague.lit</jid>" + "</never>" + "</prefs>" + "</iq>";
|
||||
|
||||
@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<Jid> 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);
|
||||
}
|
||||
|
|
|
@ -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 = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:2' queryid='testid'>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>" + "<value>"
|
||||
+ MamElements.NAMESPACE + "</value>" + "</field>" + "</x>" + "</query>" + "</iq>";
|
||||
+ MamVersion.MAM2.getNamespace() + "</value>" + "</field>" + "</x>" + "</query>" + "</iq>";
|
||||
|
||||
private static final String mamQueryResultExample = "<message to='hag66@shakespeare.lit/pda' from='coven@chat.shakespeare.lit' id='iasd207'>"
|
||||
+ "<result xmlns='urn:xmpp:mam:2' queryid='g27' id='34482-21985-73620'>"
|
||||
|
@ -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<Message> 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());
|
||||
|
||||
|
|
|
@ -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 = "<iq id='sarasa' type='set'>" + "<query xmlns='urn:xmpp:mam:2' queryid='testid'>"
|
||||
+ "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>" + "<value>"
|
||||
+ MamElements.NAMESPACE + "</value>" + "</field>" + "</x>" + "<set xmlns='http://jabber.org/protocol/rsm'>"
|
||||
+ MamVersion.MAM2.getNamespace() + "</value>" + "</field>" + "</x>" + "<set xmlns='http://jabber.org/protocol/rsm'>"
|
||||
+ "<max>10</max>" + "</set>" + "</query>" + "</iq>";
|
||||
|
||||
@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");
|
||||
|
||||
|
|
|
@ -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 = "<iq id='sarasa' type='get'>" + "<query xmlns='" + MamElements.NAMESPACE
|
||||
private static final String retrieveFormFieldStanza = "<iq id='sarasa' type='get'>" + "<query xmlns='" + MamVersion.MAM2.getNamespace()
|
||||
+ "' queryid='testid'></query>" + "</iq>";
|
||||
|
||||
private static final String additionalFieldsStanza = "<x xmlns='jabber:x:data' type='submit'>" + "<field var='FORM_TYPE'>"
|
||||
+ "<value>" + MamElements.NAMESPACE + "</value>" + "</field>"
|
||||
+ "<value>" + MamVersion.MAM2.getNamespace() + "</value>" + "</field>"
|
||||
+ "<field var='urn:example:xmpp:free-text-search'>" + "<value>Hi</value>" + "</field>"
|
||||
+ "<field var='urn:example:xmpp:stanza-content'>" + "<value>one@exampleone.org</value>" + "</field>"
|
||||
+ "</x>";
|
||||
|
||||
@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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -615,10 +615,8 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream
|
|||
if (annouceLocalStreamHost) {
|
||||
// add local proxy on first position if exists
|
||||
List<StreamHost> localProxies = getLocalStreamHost();
|
||||
if (localProxies != null) {
|
||||
streamHosts.addAll(localProxies);
|
||||
}
|
||||
}
|
||||
|
||||
// query SOCKS5 proxies for network settings
|
||||
for (Jid proxy : proxies) {
|
||||
|
@ -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<StreamHost> getLocalStreamHost() {
|
||||
// Ensure that the local SOCKS5 proxy is running (if enabled).
|
||||
Socks5Proxy.getSocks5Proxy();
|
||||
|
||||
List<StreamHost> streamHosts = new ArrayList<>();
|
||||
|
||||
XMPPConnection connection = connection();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<JingleContent> 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<JingleContent> 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<Builder, Jingle> {
|
||||
private String sid;
|
||||
|
||||
private JingleAction action;
|
||||
|
@ -186,7 +210,16 @@ public final class Jingle extends IQ {
|
|||
|
||||
private List<JingleContent> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <code>null</code>.
|
||||
* @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 <code>null</code>.
|
||||
* @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.");
|
||||
}
|
||||
|
|
|
@ -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<Jingle> {
|
||||
public class JingleProvider extends IqProvider<Jingle> {
|
||||
|
||||
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<Jingle> {
|
|||
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<Jingle> {
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,10 @@ public abstract class JingleTransportManager<D extends JingleContentTransport> i
|
|||
}
|
||||
|
||||
public XMPPConnection getConnection() {
|
||||
return connection();
|
||||
}
|
||||
|
||||
public XMPPConnection connection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager<Jing
|
|||
public Jingle createCandidateUsed(FullJid recipient, FullJid initiator, String sessionId, JingleContent.Senders contentSenders,
|
||||
JingleContent.Creator contentCreator, String contentName, String streamId,
|
||||
String candidateId) {
|
||||
Jingle.Builder jb = Jingle.getBuilder();
|
||||
Jingle.Builder jb = Jingle.builder(connection());
|
||||
jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info);
|
||||
|
||||
JingleContent.Builder cb = JingleContent.getBuilder();
|
||||
|
@ -165,7 +165,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager<Jing
|
|||
}
|
||||
|
||||
public Jingle createCandidateError(FullJid remote, FullJid initiator, String sessionId, JingleContent.Senders senders, JingleContent.Creator creator, String name, String streamId) {
|
||||
Jingle.Builder jb = Jingle.getBuilder();
|
||||
Jingle.Builder jb = Jingle.builder(connection());
|
||||
jb.setSessionId(sessionId).setInitiator(initiator).setAction(JingleAction.transport_info);
|
||||
|
||||
JingleContent.Builder cb = JingleContent.getBuilder();
|
||||
|
@ -184,7 +184,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager<Jing
|
|||
public Jingle createProxyError(FullJid remote, FullJid initiator, String sessionId,
|
||||
JingleContent.Senders senders, JingleContent.Creator creator,
|
||||
String name, String streamId) {
|
||||
Jingle.Builder jb = Jingle.getBuilder();
|
||||
Jingle.Builder jb = Jingle.builder(connection());
|
||||
jb.setSessionId(sessionId).setAction(JingleAction.transport_info).setInitiator(initiator);
|
||||
|
||||
JingleContent.Builder cb = JingleContent.getBuilder();
|
||||
|
@ -202,7 +202,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager<Jing
|
|||
public Jingle createCandidateActivated(FullJid remote, FullJid initiator, String sessionId,
|
||||
JingleContent.Senders senders, JingleContent.Creator creator,
|
||||
String name, String streamId, String candidateId) {
|
||||
Jingle.Builder jb = Jingle.getBuilder();
|
||||
Jingle.Builder jb = Jingle.builder(connection());
|
||||
jb.setInitiator(initiator).setSessionId(sessionId).setAction(JingleAction.transport_info);
|
||||
|
||||
JingleContent.Builder cb = JingleContent.getBuilder();
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.logging.Logger;
|
|||
|
||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||
import org.jivesoftware.smack.provider.ExtensionElementProvider;
|
||||
import org.jivesoftware.smack.util.DoOnce;
|
||||
import org.jivesoftware.smack.util.stringencoder.Base64;
|
||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||
|
@ -35,6 +36,8 @@ import org.jivesoftware.smackx.jiveproperties.packet.JivePropertiesExtension;
|
|||
|
||||
public class JivePropertiesExtensionProvider extends ExtensionElementProvider<JivePropertiesExtension> {
|
||||
|
||||
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<Ji
|
|||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.severe("JavaObject is not enabled. Enable with JivePropertiesManager.setJavaObjectEnabled(true)");
|
||||
LOG_OBJECT_NOT_ENABLED.once(
|
||||
() -> LOGGER.severe(
|
||||
"JavaObject is not enabled. Enable with JivePropertiesManager.setJavaObjectEnabled(true)")
|
||||
);
|
||||
}
|
||||
}
|
||||
if (name != null && value != null) {
|
||||
|
|
|
@ -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<Jid> 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 <code>true</code> if supported, <code>false</code> 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.
|
||||
*
|
||||
|
|
|
@ -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,6 +262,7 @@ public class MultiUserChat {
|
|||
listener.left(from);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Destroy destroy = mucUser.getDestroy();
|
||||
// The room has been destroyed.
|
||||
|
@ -275,7 +279,7 @@ public class MultiUserChat {
|
|||
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.
|
||||
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();
|
||||
|
||||
Presence reflectedLeavePresence = connection.createStanzaCollectorAndSend(reflectedLeavePresenceFilter, leavePresence).nextResultOrThrow();
|
||||
}
|
||||
|
||||
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 <code>null</code> if the room is not joined.
|
||||
*
|
||||
* @return the full JID of the user in the room, or <code>null</code>.
|
||||
* @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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,11 @@ import org.jivesoftware.smackx.xdata.packet.DataForm;
|
|||
public class FormNodeProvider extends EmbeddedExtensionProvider<FormNode> {
|
||||
@Override
|
||||
protected FormNode createReturnExtension(String currentElement, String currentNamespace, Map<String, String> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,12 +50,9 @@ public class ItemProvider extends ExtensionElementProvider<Item> {
|
|||
String xmlns = parser.getNamespace();
|
||||
ItemNamespace itemNamespace = ItemNamespace.fromXmlns(xmlns);
|
||||
|
||||
XmlPullParser.Event tag = parser.next();
|
||||
|
||||
if (tag == XmlPullParser.Event.END_ELEMENT) {
|
||||
return new Item(itemNamespace, id, node);
|
||||
}
|
||||
else {
|
||||
XmlPullParser.TagEvent event = parser.nextTag();
|
||||
switch (event) {
|
||||
case START_ELEMENT:
|
||||
String payloadElemName = parser.getName();
|
||||
String payloadNS = parser.getNamespace();
|
||||
|
||||
|
@ -68,6 +65,10 @@ public class ItemProvider extends ExtensionElementProvider<Item> {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -27,35 +27,26 @@ import org.jxmpp.util.XmppDateTime;
|
|||
|
||||
public class AbstractMultiFormField extends FormField {
|
||||
|
||||
private final List<String> values;
|
||||
|
||||
private final List<String> rawValues;
|
||||
private final List<Value> values;
|
||||
|
||||
protected AbstractMultiFormField(Builder<?, ?> builder) {
|
||||
super(builder);
|
||||
values = CollectionUtil.cloneAndSeal(builder.values);
|
||||
rawValues = CollectionUtil.cloneAndSeal(builder.rawValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<String> getValues() {
|
||||
public final List<Value> getRawValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<String> getRawValues() {
|
||||
return rawValues;
|
||||
}
|
||||
|
||||
public abstract static class Builder<F extends AbstractMultiFormField, B extends FormField.Builder<F, B>>
|
||||
extends FormField.Builder<F, B> {
|
||||
|
||||
private List<String> values;
|
||||
private List<String> rawValues;
|
||||
private List<Value> 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <code>false</code>, 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 <code>null</code>. 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 <code>false</code> if not explicitly set to
|
||||
* something else.
|
||||
*
|
||||
* @return the boolean value of this form field or <code>null</code> 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;
|
||||
}
|
||||
|
|
|
@ -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<String> getRawValues();
|
||||
public abstract List<Value> getRawValues();
|
||||
|
||||
private transient List<CharSequence> rawValueCharSequences;
|
||||
|
||||
public final List<CharSequence> getRawValueCharSequences() {
|
||||
if (rawValueCharSequences == null) {
|
||||
List<Value> 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<XmlElement> extraXmlChildElements;
|
||||
|
||||
/**
|
||||
* Populate @{link {@link #extraXmlChildElements}}. Note that this method may be overridden by subclasses.
|
||||
*/
|
||||
protected void populateExtraXmlChildElements() {
|
||||
List<? extends CharSequence> values = getValues();
|
||||
List<Value> 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Jid> values;
|
||||
|
||||
private final List<String> rawValues;
|
||||
private final List<Value> rawValues;
|
||||
|
||||
JidMultiFormField(Builder builder) {
|
||||
super(builder);
|
||||
|
@ -43,7 +44,7 @@ public final class JidMultiFormField extends FormField {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<String> getRawValues() {
|
||||
public List<Value> getRawValues() {
|
||||
return rawValues;
|
||||
}
|
||||
|
||||
|
@ -54,7 +55,7 @@ public final class JidMultiFormField extends FormField {
|
|||
public static final class Builder extends FormField.Builder<JidMultiFormField, JidMultiFormField.Builder> {
|
||||
private List<Jid> values;
|
||||
|
||||
private List<String> rawValues;
|
||||
private List<Value> 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String> getRawValues() {
|
||||
String rawValue = getRawValue();
|
||||
public final List<Value> 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<F extends SingleValueFormField, B extends Builder<F, B>>
|
||||
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<DataForm> {
|
|||
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<DataForm> {
|
|||
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<DataForm> {
|
|||
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;
|
||||
|
|
|
@ -20,5 +20,6 @@
|
|||
<className>org.jivesoftware.smackx.receipts.DeliveryReceiptManager</className>
|
||||
<className>org.jivesoftware.smackx.iqversion.VersionManager</className>
|
||||
<className>org.jivesoftware.smackx.caps.EntityCapsManager</className>
|
||||
<className>org.jivesoftware.smackx.softwareinfo.form.SoftwareInfoForm</className>
|
||||
</startupClasses>
|
||||
</smack>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -75,8 +75,7 @@ public class JingleContentTest extends SmackTestSuite {
|
|||
assertEquals(content1.toXML().toString(), builder.build().toXML().toString());
|
||||
|
||||
String xml =
|
||||
"<content xmlns='urn:xmpp:jingle:1' creator='initiator' disposition='session' name='A name' senders='both'>" +
|
||||
"</content>";
|
||||
"<content xmlns='urn:xmpp:jingle:1' creator='initiator' disposition='session' name='A name' senders='both'/>";
|
||||
assertEquals(xml, content1.toXML().toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
= "<iq id='test-id' type='set'>"
|
||||
+ "<jingle xmlns='urn:xmpp:jingle:1' action='content-accept' sid='MySession'>"
|
||||
+ "<content creator='initiator' name='Hello world'/>"
|
||||
+ "</jingle></iq>";
|
||||
assertEquals(expectedXml, actualXml);
|
||||
}
|
||||
}
|
|
@ -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 {
|
|||
"</file>" +
|
||||
"</description>";
|
||||
// @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'/>" +
|
||||
"</transport>";
|
||||
// @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 = "<iq from='juliet@capulet.lit/balcony'"
|
||||
+ " id='le71fa63'"
|
||||
+ " to='romeo@montague.lit/orchard'"
|
||||
+ " type='set'>"
|
||||
+ "<jingle xmlns='urn:xmpp:jingle:1'"
|
||||
+ " action='session-terminate'"
|
||||
+ " sid='a73sjjvkla37jfea'>"
|
||||
+ "<reason>"
|
||||
+ "<success/>"
|
||||
+ "<my-element xmlns='https://example.org' foo='bar'/>"
|
||||
+ "</reason>"
|
||||
+ "</jingle>"
|
||||
+ "</iq>";
|
||||
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
|
||||
"<iq from='romeo@montague.example/dr4hcr0st3lup4c'" +
|
||||
|
@ -98,9 +138,9 @@ public class JingleProviderTest {
|
|||
" to='juliet@capulet.example/yn0cl4bnw0yr3vym'" +
|
||||
" type='set'>" +
|
||||
"<jingle xmlns='urn:xmpp:jingle:1' " +
|
||||
" action='session-initiate' " +
|
||||
" initiator='romeo@montague.example/dr4hcr0st3lup4c' " +
|
||||
" sid='851ba2'>" +
|
||||
"action='session-initiate' " +
|
||||
"initiator='romeo@montague.example/dr4hcr0st3lup4c' " +
|
||||
"sid='851ba2'>" +
|
||||
"<content creator='initiator' name='a-file-offer' senders='initiator'>"
|
||||
// @formatter:on
|
||||
);
|
||||
|
@ -114,9 +154,6 @@ public class JingleProviderTest {
|
|||
// @formatter:on
|
||||
);
|
||||
|
||||
String jingleStanza = sb.toString();
|
||||
|
||||
XmlPullParser parser = PacketParserUtils.getParserFor(jingleStanza);
|
||||
return parser;
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <a href="https://igniterealtime.atlassian.net/jira/software/c/projects/SMACK/issues/SMACK-918">SMACK-918</a>
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@EnumSource(SmackTestUtil.XmlPullParserKind.class)
|
||||
public void whitespaceBeforeItemPayload(SmackTestUtil.XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException {
|
||||
String item = "<item id='13a3710c-68c3-4da2-8484-d8d9c77af91e' xmlns='http://jabber.org/protocol/pubsub#event'>"
|
||||
+ "\n <geoloc xmlns='http://jabber.org/protocol/geoloc' xml:lang='en'/>"
|
||||
+ "</item>";
|
||||
SmackTestUtil.parse(item, ItemProvider.class, parserKind);
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -115,4 +115,33 @@ public class DataFormProviderTest {
|
|||
assertEquals(2, items.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetrieveFieldWithEmptyLabel() throws XmlPullParserException, IOException, SmackParsingException {
|
||||
|
||||
String form =
|
||||
"<x xmlns='jabber:x:data' type='form'>" +
|
||||
" <title>Advanced User Search</title>" +
|
||||
" <instructions>The following fields are available for searching. Wildcard (*) characters are allowed as part of the query.</instructions>" +
|
||||
" <field var='FORM_TYPE' label='' type='hidden'>" +
|
||||
" <value>jabber:iq:search</value>" +
|
||||
" </field>" +
|
||||
" <field label='Search' var='search'>" +
|
||||
" <required/>" +
|
||||
" </field>" +
|
||||
" <field label='Username' var='Username' type='boolean'>" +
|
||||
" <value>true</value>" +
|
||||
" </field>" +
|
||||
" <field label='Name' var='Name' type='boolean'>" +
|
||||
" <value>true</value>" +
|
||||
" </field>" +
|
||||
" <field label='Email' var='Email' type='boolean'>" +
|
||||
" <value>true</value>" +
|
||||
" </field>" +
|
||||
"</x>";
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> enabledTests;
|
||||
|
||||
private final Map<String, Set<String>> enabledTestsMap;
|
||||
|
||||
public final Set<String> disabledTests;
|
||||
|
||||
private final Map<String, Set<String>> disabledTestsMap;
|
||||
|
||||
public final String defaultConnectionNickname;
|
||||
|
||||
public final Set<String> 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<String, Set<String>> convertTestsToMap(Set<String> tests) {
|
||||
Map<String, Set<String>> 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<String> getKey(Class<?> testClass, Map<String, Set<String>> 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<String, Set<String>> testsMap) {
|
||||
Set<String> 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<String, Set<String>> testsMap) {
|
||||
Class<?> testClass = method.getDeclaringClass();
|
||||
Set<String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<String> 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 {
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<GeoLocation> geoLocationListener = new PepEventListener<GeoLocation>() {
|
||||
|
||||
@Override
|
||||
public void onPepEvent(EntityBareJid jid, GeoLocation geoLocation, String id, Message message) {
|
||||
if (geoLocation.equals(geoLocation1)) {
|
||||
final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint();
|
||||
|
||||
final PepEventListener<GeoLocation> geoLocationListener = (jid, geoLocation, id, message) -> {
|
||||
if (geoLocation.equals(data)) {
|
||||
geoLocationReceived.signal();
|
||||
} else {
|
||||
geoLocationReceived.signalFailure("Received non matching GeoLocation");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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<GeoLocation> 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<GeoLocation> 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<GeoLocation> 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<GeoLocation> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<MoodElement> 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<MoodElement> 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<MoodElement> 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<MoodElement> 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<MoodElement> 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<MoodElement> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,10 +55,9 @@ public class AbstractMultiUserChatIntegrationTest extends AbstractSmackIntegrati
|
|||
if (services.isEmpty()) {
|
||||
throw new TestNotPossibleException("No MUC (XEP-45) service found");
|
||||
}
|
||||
else {
|
||||
|
||||
mucService = services.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a random room name.
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue