mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-23 04:22:05 +01:00
Compare commits
75 commits
e7372b05ea
...
11775ed6b0
Author | SHA1 | Date | |
---|---|---|---|
|
11775ed6b0 | ||
|
488055948d | ||
|
6076a9dfa5 | ||
|
0ec7e84cbc | ||
|
9ad162af6e | ||
|
9be498c440 | ||
|
25b3f35421 | ||
|
5d46e281fc | ||
|
007a04c4fe | ||
|
8e88cd2e31 | ||
|
3450ffad2b | ||
|
653d9dbba7 | ||
|
c83d717a26 | ||
|
7059b60672 | ||
|
3ded023629 | ||
|
29af92cbd1 | ||
|
b054c4fe77 | ||
|
0de0873abb | ||
|
e5bbd19ef1 | ||
|
569f7417a8 | ||
|
c4289b2c18 | ||
|
7518bf9a25 | ||
|
5da6dea138 | ||
|
7d2c3ac9f9 | ||
|
3d1a781a22 | ||
|
7f0932a481 | ||
|
f602de8771 | ||
|
f4ebd530e6 | ||
|
78ee22c261 | ||
|
456d645e27 | ||
|
3bdc1d30b1 | ||
|
5f7cfd04bd | ||
|
5a2109e73f | ||
|
4da4558b29 | ||
|
27749b2137 | ||
|
47f76952e3 | ||
|
a8044f723a | ||
|
e9b514548b | ||
|
db8e37d75f | ||
|
eccaf58df1 | ||
|
ac9641f091 | ||
|
78dcaec75b | ||
|
60f324eb1b | ||
|
09bffb8dca | ||
|
5c8e830157 | ||
|
62cba0d96f | ||
|
5273402b87 | ||
|
1a4ad7b8b5 | ||
|
4b21f003af | ||
|
b9c12d44c3 | ||
|
a2743549b8 | ||
|
d1c73eba8d | ||
|
48e3127172 | ||
|
f6da386dea | ||
|
5ddaa623da | ||
|
0c134db072 | ||
|
0332fa54d1 | ||
|
fa7297019d | ||
|
dbfc123e5e | ||
|
7ea7f9e2e9 | ||
|
b675f49b3d | ||
|
b8bd10b056 | ||
|
8b88f9cb20 | ||
|
229653af30 | ||
|
2900c5ae23 | ||
|
5f05da2f77 | ||
|
6a43481320 | ||
|
cf22371d3e | ||
|
ec982f65e2 | ||
|
89c9a41863 | ||
|
85731fbe3e | ||
|
1e21ab763c | ||
|
1aa35bc957 | ||
|
87fac888c6 | ||
|
77baaa99bc |
54 changed files with 1120 additions and 173 deletions
|
@ -6,6 +6,11 @@
|
||||||
<module name="SuppressionFilter">
|
<module name="SuppressionFilter">
|
||||||
<property name="file" value="config/suppressions.xml"/>
|
<property name="file" value="config/suppressions.xml"/>
|
||||||
</module>
|
</module>
|
||||||
|
<module name="SuppressWithPlainTextCommentFilter">
|
||||||
|
<property name="offCommentFormat" value="CHECKSTYLE\:OFF\:(\w+)"/>
|
||||||
|
<property name="onCommentFormat" value="CHECKSTYLE\:ON\:(\w+)"/>
|
||||||
|
<property name="checkFormat" value="$1"/>
|
||||||
|
</module>
|
||||||
<module name="Header">
|
<module name="Header">
|
||||||
<property name="headerFile" value="config/${checkstyleLicenseHeader}.txt"/>
|
<property name="headerFile" value="config/${checkstyleLicenseHeader}.txt"/>
|
||||||
<property name="ignoreLines" value="3"/>
|
<property name="ignoreLines" value="3"/>
|
||||||
|
@ -61,6 +66,14 @@
|
||||||
<property name="format" value="^\s*//[^\s]"/>
|
<property name="format" value="^\s*//[^\s]"/>
|
||||||
<property name="message" value="Comment start ('//') followed by non-space character. You would not continue after a punctuation without a space, would you?"/>
|
<property name="message" value="Comment start ('//') followed by non-space character. You would not continue after a punctuation without a space, would you?"/>
|
||||||
</module>
|
</module>
|
||||||
|
<!-- Check for synchronized keyword on Manager's static
|
||||||
|
getInstanceFor() method. Note that if XMPPConnection is every
|
||||||
|
replaced with something else, then we need to change it here
|
||||||
|
too. -->
|
||||||
|
<module name="RegexpSingleline">
|
||||||
|
<property name="format" value="^\s*public(?!.*synchronized).*getInstanceFor\(XMPPConnection.*$"/>
|
||||||
|
<property name="message" value="getInstanceFor() should be synchronized"/>
|
||||||
|
</module>
|
||||||
<module name="JavadocPackage"/>
|
<module name="JavadocPackage"/>
|
||||||
<module name="TreeWalker">
|
<module name="TreeWalker">
|
||||||
<module name="SuppressionCommentFilter"/>
|
<module name="SuppressionCommentFilter"/>
|
||||||
|
@ -89,6 +102,11 @@
|
||||||
<property name="message" value="Usage of println"/>
|
<property name="message" value="Usage of println"/>
|
||||||
<property name="ignoreComments" value="true"/>
|
<property name="ignoreComments" value="true"/>
|
||||||
</module>
|
</module>
|
||||||
|
<module name="RegexpSinglelineJava">
|
||||||
|
<property name="format" value="Boolean\.valueOf\("/>
|
||||||
|
<property name="message" value="Usage Boolean.valueOf(), consider using ParserUtils.parseXmlBoolean() instead (if you want to parse xs:boolean values)"/>
|
||||||
|
<property name="ignoreComments" value="true"/>
|
||||||
|
</module>
|
||||||
<module name="RegexpSinglelineJava">
|
<module name="RegexpSinglelineJava">
|
||||||
<property name="format" value="^\t+"/>
|
<property name="format" value="^\t+"/>
|
||||||
<property name="message" value="Indent must not use tab characters. Use space instead."/>
|
<property name="message" value="Indent must not use tab characters. Use space instead."/>
|
||||||
|
|
|
@ -141,6 +141,74 @@ hr {
|
||||||
|
|
||||||
<div id="pageBody">
|
<div id="pageBody">
|
||||||
|
|
||||||
|
<h2>4.3.3 -- <span style="font-weight: normal;">2019-03-14</span></h2>
|
||||||
|
|
||||||
|
<h2> Bug
|
||||||
|
</h2>
|
||||||
|
<ul>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-856'>SMACK-856</a>] - Smack fails under JDK 11 because com.sun.jndi.dns.DnsContextFactory is not inaccessible
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2> Improvement
|
||||||
|
</h2>
|
||||||
|
<ul>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-858'>SMACK-858</a>] - Dependency version specifier of jxmpp and MiniDNS include alpha/beta/... versions of the follow up version when Maven is used
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-859'>SMACK-859</a>] - MultiUserChat enter() should reset the timeout of the collector waiting for the final self presence to prevent timeouts for large MUCs
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>4.3.2 -- <span style="font-weight: normal;">2019-02-22</span></h2>
|
||||||
|
|
||||||
|
<h2> Bug
|
||||||
|
</h2>
|
||||||
|
<ul>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-842'>SMACK-842</a>] - The RFC 3920 xml-not-well-formed error condition should be handled in stream error not a stanza error
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-843'>SMACK-843</a>] - ManManager.pagePrevious() pages into the wrong direction
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-844'>SMACK-844</a>] - Check if bounded unacknowledged stanzas queue is full before adding to it to avoid IllegalStateException
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-845'>SMACK-845</a>] - Ensure that IQ response 'to' address and ID are set correctly
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-846'>SMACK-846</a>] - XMPPTCPConnection does not wait for stream features after authentication if compression is disabled
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-848'>SMACK-848</a>] - Make MultiUserChat.leave() wait for response
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-850'>SMACK-850</a>] - DeliveryReceiptManager should not send receipts with messages of type 'groupchat'
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-855'>SMACK-855</a>] - XMPPTCPConnection sometimes has two writer threads running
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2> Improvement
|
||||||
|
</h2>
|
||||||
|
<ul>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-847'>SMACK-847</a>] - Make TCP socket connection attempt interruptable
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-849'>SMACK-849</a>] - Smack Local SOCKS5 Proxy thread should be marked as daemon thread
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>4.3.1 -- <span style="font-weight: normal;">2018-10-14</span></h2>
|
||||||
|
|
||||||
|
<h2> Bug
|
||||||
|
</h2>
|
||||||
|
<ul>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-833'>SMACK-833</a>] - XMLUtil.prettyFormatXml() throws on some Android devices
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2> Improvement
|
||||||
|
</h2>
|
||||||
|
<ul>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-829'>SMACK-829</a>] - Disconnect BOSH client on shutdown
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-838'>SMACK-838</a>] - FormField.getFirstValue() throws IndexOutOfBoundsException if there are no values
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<h2>4.3.0 -- <span style="font-weight: normal;">2018-08-02</span></h2>
|
<h2>4.3.0 -- <span style="font-weight: normal;">2018-08-02</span></h2>
|
||||||
|
|
||||||
<h2> Bug
|
<h2> Bug
|
||||||
|
|
|
@ -4,5 +4,7 @@ This API is considered beta quality."""
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':smack-core')
|
compile project(':smack-core')
|
||||||
compile 'org.igniterealtime.jbosh:jbosh:[0.9,0.10)'
|
// See https://issues.igniterealtime.org/browse/SMACK-858 and
|
||||||
|
// comment in version.gradle why the specify the version this way.
|
||||||
|
compile 'org.igniterealtime.jbosh:jbosh:[0.9.1,0.9.999]'
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.jivesoftware.smack.bosh;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||||
import org.jivesoftware.smack.proxy.ProxyInfo;
|
import org.jivesoftware.smack.proxy.ProxyInfo;
|
||||||
|
@ -34,6 +36,7 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
||||||
|
|
||||||
private final boolean https;
|
private final boolean https;
|
||||||
private final String file;
|
private final String file;
|
||||||
|
private Map<String, String> httpHeaders;
|
||||||
|
|
||||||
private BOSHConfiguration(Builder builder) {
|
private BOSHConfiguration(Builder builder) {
|
||||||
super(builder);
|
super(builder);
|
||||||
|
@ -49,6 +52,7 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
||||||
} else {
|
} else {
|
||||||
file = builder.file;
|
file = builder.file;
|
||||||
}
|
}
|
||||||
|
httpHeaders = builder.httpHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isProxyEnabled() {
|
public boolean isProxyEnabled() {
|
||||||
|
@ -76,6 +80,10 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
||||||
return new URI((https ? "https://" : "http://") + this.host + ":" + this.port + file);
|
return new URI((https ? "https://" : "http://") + this.host + ":" + this.port + file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getHttpHeaders() {
|
||||||
|
return httpHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
@ -83,6 +91,7 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
||||||
public static final class Builder extends ConnectionConfiguration.Builder<Builder, BOSHConfiguration> {
|
public static final class Builder extends ConnectionConfiguration.Builder<Builder, BOSHConfiguration> {
|
||||||
private boolean https;
|
private boolean https;
|
||||||
private String file;
|
private String file;
|
||||||
|
private Map<String, String> httpHeaders = new HashMap<>();
|
||||||
|
|
||||||
private Builder() {
|
private Builder() {
|
||||||
}
|
}
|
||||||
|
@ -101,6 +110,11 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder addHttpHeader(String name, String value) {
|
||||||
|
httpHeaders.put(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BOSHConfiguration build() {
|
public BOSHConfiguration build() {
|
||||||
return new BOSHConfiguration(this);
|
return new BOSHConfiguration(this);
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.io.PipedReader;
|
||||||
import java.io.PipedWriter;
|
import java.io.PipedWriter;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@ -156,6 +157,9 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
if (config.isProxyEnabled()) {
|
if (config.isProxyEnabled()) {
|
||||||
cfgBuilder.setProxy(config.getProxyAddress(), config.getProxyPort());
|
cfgBuilder.setProxy(config.getProxyAddress(), config.getProxyPort());
|
||||||
}
|
}
|
||||||
|
for (Map.Entry<String, String> h : config.getHttpHeaders().entrySet()) {
|
||||||
|
cfgBuilder.addHttpHeader(h.getKey(), h.getValue());
|
||||||
|
}
|
||||||
client = BOSHClient.create(cfgBuilder.build());
|
client = BOSHClient.create(cfgBuilder.build());
|
||||||
|
|
||||||
client.addBOSHClientConnListener(new BOSHConnectionListener());
|
client.addBOSHClientConnListener(new BOSHConnectionListener());
|
||||||
|
@ -263,6 +267,11 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
client = null;
|
client = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instantShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void instantShutdown() {
|
||||||
setWasAuthenticated();
|
setWasAuthenticated();
|
||||||
sessionID = null;
|
sessionID = null;
|
||||||
done = true;
|
done = true;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
@ -235,7 +236,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
* stanza is send by the server. This is set to true once the last feature stanza has been
|
* stanza is send by the server. This is set to true once the last feature stanza has been
|
||||||
* parsed.
|
* parsed.
|
||||||
*/
|
*/
|
||||||
protected final SynchronizationPoint<Exception> lastFeaturesReceived = new SynchronizationPoint<Exception>(
|
protected final SynchronizationPoint<SmackException> lastFeaturesReceived = new SynchronizationPoint<>(
|
||||||
AbstractXMPPConnection.this, "last stream features received from server");
|
AbstractXMPPConnection.this, "last stream features received from server");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -297,7 +298,18 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
private static final AsyncButOrdered<AbstractXMPPConnection> ASYNC_BUT_ORDERED = new AsyncButOrdered<>();
|
protected static final AsyncButOrdered<AbstractXMPPConnection> ASYNC_BUT_ORDERED = new AsyncButOrdered<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An executor which uses {@link #asyncGoLimited(Runnable)} to limit the number of asynchronously processed runnables
|
||||||
|
* per connection.
|
||||||
|
*/
|
||||||
|
private final Executor limitedExcutor = new Executor() {
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable runnable) {
|
||||||
|
asyncGoLimited(runnable);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The used host to establish the connection to
|
* The used host to establish the connection to
|
||||||
|
@ -314,6 +326,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
*/
|
*/
|
||||||
protected boolean authenticated = false;
|
protected boolean authenticated = false;
|
||||||
|
|
||||||
|
// TODO: Migrate to ZonedDateTime once Smack's minimum required Android SDK level is 26 (8.0, Oreo) or higher.
|
||||||
|
protected long authenticatedConnectionInitiallyEstablishedTimestamp;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag that indicates if the user was authenticated with the server when the connection
|
* Flag that indicates if the user was authenticated with the server when the connection
|
||||||
* to the server was closed (abruptly or not).
|
* to the server was closed (abruptly or not).
|
||||||
|
@ -381,6 +396,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
@Override
|
@Override
|
||||||
public abstract boolean isUsingCompression();
|
public abstract boolean isUsingCompression();
|
||||||
|
|
||||||
|
protected void initState() {
|
||||||
|
saslFeatureReceived.init();
|
||||||
|
lastFeaturesReceived.init();
|
||||||
|
tlsHandled.init();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establishes a connection to the XMPP server. It basically
|
* Establishes a connection to the XMPP server. It basically
|
||||||
* creates and maintains a connection to the server.
|
* creates and maintains a connection to the server.
|
||||||
|
@ -399,21 +420,23 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
throwAlreadyConnectedExceptionIfAppropriate();
|
throwAlreadyConnectedExceptionIfAppropriate();
|
||||||
|
|
||||||
// Reset the connection state
|
// Reset the connection state
|
||||||
|
initState();
|
||||||
saslAuthentication.init();
|
saslAuthentication.init();
|
||||||
saslFeatureReceived.init();
|
|
||||||
lastFeaturesReceived.init();
|
|
||||||
tlsHandled.init();
|
|
||||||
streamId = null;
|
streamId = null;
|
||||||
|
|
||||||
// Perform the actual connection to the XMPP service
|
try {
|
||||||
connectInternal();
|
// Perform the actual connection to the XMPP service
|
||||||
|
connectInternal();
|
||||||
|
|
||||||
// If TLS is required but the server doesn't offer it, disconnect
|
// If TLS is required but the server doesn't offer it, disconnect
|
||||||
// from the server and throw an error. First check if we've already negotiated TLS
|
// from the server and throw an error. First check if we've already negotiated TLS
|
||||||
// and are secure, however (features get parsed a second time after TLS is established).
|
// and are secure, however (features get parsed a second time after TLS is established).
|
||||||
if (!isSecureConnection() && getConfiguration().getSecurityMode() == SecurityMode.required) {
|
if (!isSecureConnection() && getConfiguration().getSecurityMode() == SecurityMode.required) {
|
||||||
shutdown();
|
throw new SecurityRequiredByClientException();
|
||||||
throw new SecurityRequiredByClientException();
|
}
|
||||||
|
} catch (SmackException | IOException | XMPPException | InterruptedException e) {
|
||||||
|
instantShutdown();
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make note of the fact that we're now connected.
|
// Make note of the fact that we're now connected.
|
||||||
|
@ -550,7 +573,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
// - the servers last features stanza has been parsed
|
// - the servers last features stanza has been parsed
|
||||||
// - the timeout occurs
|
// - the timeout occurs
|
||||||
LOGGER.finer("Waiting for last features to be received before continuing with resource binding");
|
LOGGER.finer("Waiting for last features to be received before continuing with resource binding");
|
||||||
lastFeaturesReceived.checkIfSuccessOrWait();
|
lastFeaturesReceived.checkIfSuccessOrWaitOrThrow();
|
||||||
|
|
||||||
|
|
||||||
if (!hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
|
if (!hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
|
||||||
|
@ -582,6 +605,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException, InterruptedException {
|
protected void afterSuccessfulLogin(final boolean resumed) throws NotConnectedException, InterruptedException {
|
||||||
|
if (!resumed) {
|
||||||
|
authenticatedConnectionInitiallyEstablishedTimestamp = System.currentTimeMillis();
|
||||||
|
}
|
||||||
// Indicate that we're now authenticated.
|
// Indicate that we're now authenticated.
|
||||||
this.authenticated = true;
|
this.authenticated = true;
|
||||||
|
|
||||||
|
@ -763,6 +789,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
*/
|
*/
|
||||||
protected abstract void shutdown();
|
protected abstract void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs an unclean disconnect and shutdown of the connection. Does not send a closing stream stanza.
|
||||||
|
*/
|
||||||
|
public abstract void instantShutdown();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addConnectionListener(ConnectionListener connectionListener) {
|
public void addConnectionListener(ConnectionListener connectionListener) {
|
||||||
if (connectionListener == null) {
|
if (connectionListener == null) {
|
||||||
|
@ -1097,6 +1128,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
if (packet instanceof IQ) {
|
if (packet instanceof IQ) {
|
||||||
final IQ iq = (IQ) packet;
|
final IQ iq = (IQ) packet;
|
||||||
if (iq.isRequestIQ()) {
|
if (iq.isRequestIQ()) {
|
||||||
|
final IQ iqRequest = iq;
|
||||||
final String key = XmppStringUtils.generateKey(iq.getChildElementName(), iq.getChildElementNamespace());
|
final String key = XmppStringUtils.generateKey(iq.getChildElementName(), iq.getChildElementNamespace());
|
||||||
IQRequestHandler iqRequestHandler;
|
IQRequestHandler iqRequestHandler;
|
||||||
final IQ.Type type = iq.getType();
|
final IQ.Type type = iq.getType();
|
||||||
|
@ -1146,7 +1178,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
executorService = ASYNC_BUT_ORDERED.asExecutorFor(this);
|
executorService = ASYNC_BUT_ORDERED.asExecutorFor(this);
|
||||||
break;
|
break;
|
||||||
case async:
|
case async:
|
||||||
executorService = CACHED_EXECUTOR_SERVICE;
|
executorService = limitedExcutor;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
final IQRequestHandler finalIqRequestHandler = iqRequestHandler;
|
final IQRequestHandler finalIqRequestHandler = iqRequestHandler;
|
||||||
|
@ -1162,6 +1194,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
// e.g. to avoid presence leaks.
|
// e.g. to avoid presence leaks.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert (response.getType() == IQ.Type.result || response.getType() == IQ.Type.error);
|
||||||
|
|
||||||
|
response.setTo(iqRequest.getFrom());
|
||||||
|
response.setStanzaId(iqRequest.getStanzaId());
|
||||||
try {
|
try {
|
||||||
sendStanza(response);
|
sendStanza(response);
|
||||||
}
|
}
|
||||||
|
@ -1191,7 +1228,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final StanzaListener listener : listenersToNotify) {
|
for (final StanzaListener listener : listenersToNotify) {
|
||||||
asyncGo(new Runnable() {
|
asyncGoLimited(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
|
@ -1710,6 +1747,18 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
return lastStanzaReceived;
|
return lastStanzaReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the timestamp when the connection was the first time authenticated, i.e., when the first successful login was
|
||||||
|
* performed. Note that this value is not reset on disconnect, so it represents the timestamp from the last
|
||||||
|
* authenticated connection. The value is also not reset on stream resumption.
|
||||||
|
*
|
||||||
|
* @return the timestamp or {@code null}.
|
||||||
|
* @since 4.3.3
|
||||||
|
*/
|
||||||
|
public final long getAuthenticatedConnectionInitiallyEstablishedTimestamp() {
|
||||||
|
return authenticatedConnectionInitiallyEstablishedTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install a parsing exception callback, which will be invoked once an exception is encountered while parsing a
|
* Install a parsing exception callback, which will be invoked once an exception is encountered while parsing a
|
||||||
* stanza.
|
* stanza.
|
||||||
|
@ -1736,6 +1785,75 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
return getClass().getSimpleName() + '[' + localEndpointString + "] (" + getConnectionCounter() + ')';
|
return getClass().getSimpleName() + '[' + localEndpointString + "] (" + getConnectionCounter() + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A queue of deferred runnables that where not executed immediately because {@link #currentAsyncRunnables} reached
|
||||||
|
* {@link #maxAsyncRunnables}. Note that we use a {@code LinkedList} in order to avoid space blowups in case the
|
||||||
|
* list ever becomes very big and shrinks again.
|
||||||
|
*/
|
||||||
|
private final Queue<Runnable> deferredAsyncRunnables = new LinkedList<>();
|
||||||
|
|
||||||
|
private int deferredAsyncRunnablesCount;
|
||||||
|
|
||||||
|
private int deferredAsyncRunnablesCountPrevious;
|
||||||
|
|
||||||
|
private int maxAsyncRunnables = SmackConfiguration.getDefaultConcurrencyLevelLimit();
|
||||||
|
|
||||||
|
private int currentAsyncRunnables;
|
||||||
|
|
||||||
|
protected void asyncGoLimited(final Runnable runnable) {
|
||||||
|
Runnable wrappedRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
runnable.run();
|
||||||
|
|
||||||
|
synchronized (deferredAsyncRunnables) {
|
||||||
|
Runnable defferredRunnable = deferredAsyncRunnables.poll();
|
||||||
|
if (defferredRunnable == null) {
|
||||||
|
currentAsyncRunnables--;
|
||||||
|
} else {
|
||||||
|
deferredAsyncRunnablesCount--;
|
||||||
|
asyncGo(defferredRunnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
synchronized (deferredAsyncRunnables) {
|
||||||
|
if (currentAsyncRunnables < maxAsyncRunnables) {
|
||||||
|
currentAsyncRunnables++;
|
||||||
|
asyncGo(wrappedRunnable);
|
||||||
|
} else {
|
||||||
|
deferredAsyncRunnablesCount++;
|
||||||
|
deferredAsyncRunnables.add(wrappedRunnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int HIGH_WATERMARK = 100;
|
||||||
|
final int INFORM_WATERMARK = 20;
|
||||||
|
|
||||||
|
final int deferredAsyncRunnablesCount = this.deferredAsyncRunnablesCount;
|
||||||
|
|
||||||
|
if (deferredAsyncRunnablesCount >= HIGH_WATERMARK
|
||||||
|
&& deferredAsyncRunnablesCountPrevious < HIGH_WATERMARK) {
|
||||||
|
LOGGER.log(Level.WARNING, "High watermark of " + HIGH_WATERMARK + " simultaneous executing runnables reached");
|
||||||
|
} else if (deferredAsyncRunnablesCount >= INFORM_WATERMARK
|
||||||
|
&& deferredAsyncRunnablesCountPrevious < INFORM_WATERMARK) {
|
||||||
|
LOGGER.log(Level.INFO, INFORM_WATERMARK + " simultaneous executing runnables reached");
|
||||||
|
}
|
||||||
|
|
||||||
|
deferredAsyncRunnablesCountPrevious = deferredAsyncRunnablesCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaxAsyncOperations(int maxAsyncOperations) {
|
||||||
|
if (maxAsyncOperations < 1) {
|
||||||
|
throw new IllegalArgumentException("Max async operations must be greater than 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (deferredAsyncRunnables) {
|
||||||
|
maxAsyncRunnables = maxAsyncOperations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected static void asyncGo(Runnable runnable) {
|
protected static void asyncGo(Runnable runnable) {
|
||||||
CACHED_EXECUTOR_SERVICE.execute(runnable);
|
CACHED_EXECUTOR_SERVICE.execute(runnable);
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,16 @@ public class AsyncButOrdered<K> {
|
||||||
|
|
||||||
private final Map<K, Boolean> threadActiveMap = new WeakHashMap<>();
|
private final Map<K, Boolean> threadActiveMap = new WeakHashMap<>();
|
||||||
|
|
||||||
|
private final Executor executor;
|
||||||
|
|
||||||
|
public AsyncButOrdered() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AsyncButOrdered(Executor executor) {
|
||||||
|
this.executor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoke the given {@link Runnable} asynchronous but ordered in respect to the given key.
|
* Invoke the given {@link Runnable} asynchronous but ordered in respect to the given key.
|
||||||
*
|
*
|
||||||
|
@ -86,7 +96,11 @@ public class AsyncButOrdered<K> {
|
||||||
if (newHandler) {
|
if (newHandler) {
|
||||||
Handler handler = new Handler(keyQueue, key);
|
Handler handler = new Handler(keyQueue, key);
|
||||||
threadActiveMap.put(key, true);
|
threadActiveMap.put(key, true);
|
||||||
AbstractXMPPConnection.asyncGo(handler);
|
if (executor == null) {
|
||||||
|
AbstractXMPPConnection.asyncGo(handler);
|
||||||
|
} else {
|
||||||
|
executor.execute(handler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.jivesoftware.smack;
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -45,6 +46,7 @@ import org.jxmpp.jid.impl.JidCreate;
|
||||||
import org.jxmpp.jid.parts.Resourcepart;
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
import org.jxmpp.stringprep.XmppStringprepException;
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
import org.minidns.dnsname.DnsName;
|
import org.minidns.dnsname.DnsName;
|
||||||
|
import org.minidns.util.InetAddressUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration to use while establishing the connection to the server.
|
* Configuration to use while establishing the connection to the server.
|
||||||
|
@ -171,6 +173,14 @@ public abstract class ConnectionConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DnsName getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
InetAddress getHostAddress() {
|
||||||
|
return hostAddress;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the server name of the target server.
|
* Returns the server name of the target server.
|
||||||
*
|
*
|
||||||
|
@ -642,6 +652,33 @@ public abstract class ConnectionConfiguration {
|
||||||
return getThis();
|
return getThis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the host to connect to by either its fully qualified domain name (FQDN) or its IP.
|
||||||
|
*
|
||||||
|
* @param fqdnOrIp a CharSequence either representing the FQDN or the IP of the host.
|
||||||
|
* @return a reference to this builder.
|
||||||
|
* @see #setHost(DnsName)
|
||||||
|
* @see #setHostAddress(InetAddress)
|
||||||
|
* @since 4.3.2
|
||||||
|
*/
|
||||||
|
public B setHostAddressByNameOrIp(CharSequence fqdnOrIp) {
|
||||||
|
String fqdnOrIpString = fqdnOrIp.toString();
|
||||||
|
if (InetAddressUtil.isIpAddress(fqdnOrIp)) {
|
||||||
|
InetAddress hostInetAddress;
|
||||||
|
try {
|
||||||
|
hostInetAddress = InetAddress.getByName(fqdnOrIpString);
|
||||||
|
}
|
||||||
|
catch (UnknownHostException e) {
|
||||||
|
// Should never happen.
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
setHostAddress(hostInetAddress);
|
||||||
|
} else {
|
||||||
|
setHost(fqdnOrIpString);
|
||||||
|
}
|
||||||
|
return getThis();
|
||||||
|
}
|
||||||
|
|
||||||
public B setPort(int port) {
|
public B setPort(int port) {
|
||||||
if (port < 0 || port > 65535) {
|
if (port < 0 || port > 65535) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
|
|
|
@ -365,4 +365,19 @@ public final class SmackConfiguration {
|
||||||
public static void setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode unknownIqRequestReplyMode) {
|
public static void setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode unknownIqRequestReplyMode) {
|
||||||
SmackConfiguration.unknownIqRequestReplyMode = Objects.requireNonNull(unknownIqRequestReplyMode, "Must set mode");
|
SmackConfiguration.unknownIqRequestReplyMode = Objects.requireNonNull(unknownIqRequestReplyMode, "Must set mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int defaultConcurrencyLevelLimit;
|
||||||
|
|
||||||
|
static {
|
||||||
|
int availableProcessors = Runtime.getRuntime().availableProcessors();
|
||||||
|
if (availableProcessors < 8) {
|
||||||
|
defaultConcurrencyLevelLimit = 8;
|
||||||
|
} else {
|
||||||
|
defaultConcurrencyLevelLimit = (int) (availableProcessors * 1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getDefaultConcurrencyLevelLimit() {
|
||||||
|
return defaultConcurrencyLevelLimit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,13 +93,24 @@ public class SmackException extends Exception {
|
||||||
return new NoResponseException(sb.toString());
|
return new NoResponseException(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
// TODO: Remove in Smack 4.4.
|
||||||
public static NoResponseException newWith(XMPPConnection connection,
|
public static NoResponseException newWith(XMPPConnection connection,
|
||||||
StanzaCollector collector) {
|
StanzaCollector collector) {
|
||||||
return newWith(connection, collector.getStanzaFilter());
|
return newWith(connection, collector.getStanzaFilter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static NoResponseException newWith(long timeout,
|
||||||
|
StanzaCollector collector) {
|
||||||
|
return newWith(timeout, collector.getStanzaFilter());
|
||||||
|
}
|
||||||
|
|
||||||
public static NoResponseException newWith(XMPPConnection connection, StanzaFilter filter) {
|
public static NoResponseException newWith(XMPPConnection connection, StanzaFilter filter) {
|
||||||
final StringBuilder sb = getWaitingFor(connection);
|
return newWith(connection.getReplyTimeout(), filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NoResponseException newWith(long timeout, StanzaFilter filter) {
|
||||||
|
final StringBuilder sb = getWaitingFor(timeout);
|
||||||
sb.append(" Waited for response using: ");
|
sb.append(" Waited for response using: ");
|
||||||
if (filter != null) {
|
if (filter != null) {
|
||||||
sb.append(filter.toString());
|
sb.append(filter.toString());
|
||||||
|
@ -112,7 +123,10 @@ public class SmackException extends Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static StringBuilder getWaitingFor(XMPPConnection connection) {
|
private static StringBuilder getWaitingFor(XMPPConnection connection) {
|
||||||
final long replyTimeout = connection.getReplyTimeout();
|
return getWaitingFor(connection.getReplyTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder getWaitingFor(final long replyTimeout) {
|
||||||
final StringBuilder sb = new StringBuilder(256);
|
final StringBuilder sb = new StringBuilder(256);
|
||||||
sb.append("No response received within reply timeout. Timeout was "
|
sb.append("No response received within reply timeout. Timeout was "
|
||||||
+ replyTimeout + "ms (~"
|
+ replyTimeout + "ms (~"
|
||||||
|
@ -334,4 +348,16 @@ public class SmackException extends Exception {
|
||||||
super("Resource binding was not offered by server");
|
super("Resource binding was not offered by server");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SmackWrappedException extends SmackException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public SmackWrappedException(Exception exception) {
|
||||||
|
super(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,18 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack;
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.util.CallbackRecipient;
|
import org.jivesoftware.smack.util.CallbackRecipient;
|
||||||
|
@ -29,6 +36,8 @@ import org.jivesoftware.smack.util.SuccessCallback;
|
||||||
|
|
||||||
public abstract class SmackFuture<V, E extends Exception> implements Future<V>, CallbackRecipient<V, E> {
|
public abstract class SmackFuture<V, E extends Exception> implements Future<V>, CallbackRecipient<V, E> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(SmackFuture.class.getName());
|
||||||
|
|
||||||
private boolean cancelled;
|
private boolean cancelled;
|
||||||
|
|
||||||
protected V result;
|
protected V result;
|
||||||
|
@ -94,7 +103,7 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
||||||
@Override
|
@Override
|
||||||
public final synchronized V get() throws InterruptedException, ExecutionException {
|
public final synchronized V get() throws InterruptedException, ExecutionException {
|
||||||
while (result == null && exception == null && !cancelled) {
|
while (result == null && exception == null && !cancelled) {
|
||||||
wait();
|
futureWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
return getOrThrowExecutionException();
|
return getOrThrowExecutionException();
|
||||||
|
@ -102,7 +111,7 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
||||||
|
|
||||||
public final synchronized V getOrThrow() throws E, InterruptedException {
|
public final synchronized V getOrThrow() throws E, InterruptedException {
|
||||||
while (result == null && exception == null && !cancelled) {
|
while (result == null && exception == null && !cancelled) {
|
||||||
wait();
|
futureWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception != null) {
|
if (exception != null) {
|
||||||
|
@ -124,7 +133,7 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
||||||
while (result != null && exception != null) {
|
while (result != null && exception != null) {
|
||||||
final long waitTimeRemaining = deadline - System.currentTimeMillis();
|
final long waitTimeRemaining = deadline - System.currentTimeMillis();
|
||||||
if (waitTimeRemaining > 0) {
|
if (waitTimeRemaining > 0) {
|
||||||
wait(waitTimeRemaining);
|
futureWait(waitTimeRemaining);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +171,15 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected final void futureWait() throws InterruptedException {
|
||||||
|
futureWait(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WaitNotInLoop")
|
||||||
|
protected void futureWait(long timeout) throws InterruptedException {
|
||||||
|
wait(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
public static class InternalSmackFuture<V, E extends Exception> extends SmackFuture<V, E> {
|
public static class InternalSmackFuture<V, E extends Exception> extends SmackFuture<V, E> {
|
||||||
public final synchronized void setResult(V result) {
|
public final synchronized void setResult(V result) {
|
||||||
this.result = result;
|
this.result = result;
|
||||||
|
@ -178,6 +196,64 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SocketFuture extends InternalSmackFuture<Socket, IOException> {
|
||||||
|
private final Socket socket;
|
||||||
|
|
||||||
|
private final Object wasInterruptedLock = new Object();
|
||||||
|
|
||||||
|
private boolean wasInterrupted;
|
||||||
|
|
||||||
|
public SocketFuture(SocketFactory socketFactory) throws IOException {
|
||||||
|
socket = socketFactory.createSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void futureWait(long timeout) throws InterruptedException {
|
||||||
|
try {
|
||||||
|
super.futureWait(timeout);
|
||||||
|
} catch (InterruptedException interruptedException) {
|
||||||
|
synchronized (wasInterruptedLock) {
|
||||||
|
wasInterrupted = true;
|
||||||
|
if (!socket.isClosed()) {
|
||||||
|
closeSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw interruptedException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connectAsync(final SocketAddress socketAddress, final int timeout) {
|
||||||
|
AbstractXMPPConnection.asyncGo(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
socket.connect(socketAddress, timeout);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
setException(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
synchronized (wasInterruptedLock) {
|
||||||
|
if (wasInterrupted) {
|
||||||
|
closeSocket();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setResult(socket);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeSocket() {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
catch (IOException ioException) {
|
||||||
|
LOGGER.log(Level.WARNING, "Could not close socket", ioException);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public abstract static class InternalProcessStanzaSmackFuture<V, E extends Exception> extends InternalSmackFuture<V, E>
|
public abstract static class InternalProcessStanzaSmackFuture<V, E extends Exception> extends InternalSmackFuture<V, E>
|
||||||
implements StanzaListener, ExceptionCallback<E> {
|
implements StanzaListener, ExceptionCallback<E> {
|
||||||
|
|
||||||
|
|
|
@ -262,7 +262,7 @@ public class StanzaCollector {
|
||||||
if (!connection.isConnected()) {
|
if (!connection.isConnected()) {
|
||||||
throw new NotConnectedException(connection, packetFilter);
|
throw new NotConnectedException(connection, packetFilter);
|
||||||
}
|
}
|
||||||
throw NoResponseException.newWith(connection, this);
|
throw NoResponseException.newWith(timeout, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
XMPPErrorException.ifHasErrorThenThrow(result);
|
XMPPErrorException.ifHasErrorThenThrow(result);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2014-2015 Florian Schmaus
|
* Copyright © 2014-2019 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -22,6 +22,7 @@ import java.util.concurrent.locks.Lock;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.SmackException.SmackWrappedException;
|
||||||
import org.jivesoftware.smack.packet.Nonza;
|
import org.jivesoftware.smack.packet.Nonza;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
@ -37,6 +38,7 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
// same memory synchronization effects as synchronization block enter and leave.
|
// same memory synchronization effects as synchronization block enter and leave.
|
||||||
private State state;
|
private State state;
|
||||||
private E failureException;
|
private E failureException;
|
||||||
|
private SmackWrappedException smackWrappedExcpetion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new synchronization point for the given connection.
|
* Construct a new synchronization point for the given connection.
|
||||||
|
@ -59,6 +61,7 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
connectionLock.lock();
|
connectionLock.lock();
|
||||||
state = State.Initial;
|
state = State.Initial;
|
||||||
failureException = null;
|
failureException = null;
|
||||||
|
smackWrappedExcpetion = null;
|
||||||
connectionLock.unlock();
|
connectionLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +74,7 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
* @throws InterruptedException if the connection is interrupted.
|
* @throws InterruptedException if the connection is interrupted.
|
||||||
* @return <code>null</code> if synchronization point was successful, or the failure Exception.
|
* @return <code>null</code> if synchronization point was successful, or the failure Exception.
|
||||||
*/
|
*/
|
||||||
public E sendAndWaitForResponse(TopLevelStreamElement request) throws NoResponseException,
|
public Exception sendAndWaitForResponse(TopLevelStreamElement request) throws NoResponseException,
|
||||||
NotConnectedException, InterruptedException {
|
NotConnectedException, InterruptedException {
|
||||||
assert (state == State.Initial);
|
assert (state == State.Initial);
|
||||||
connectionLock.lock();
|
connectionLock.lock();
|
||||||
|
@ -103,15 +106,14 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
* @throws NoResponseException if no response was received.
|
* @throws NoResponseException if no response was received.
|
||||||
* @throws NotConnectedException if the connection is not connected.
|
* @throws NotConnectedException if the connection is not connected.
|
||||||
* @throws InterruptedException if the connection is interrupted.
|
* @throws InterruptedException if the connection is interrupted.
|
||||||
|
* @throws SmackWrappedException in case of a wrapped exception;
|
||||||
*/
|
*/
|
||||||
public void sendAndWaitForResponseOrThrow(Nonza request) throws E, NoResponseException,
|
public void sendAndWaitForResponseOrThrow(Nonza request) throws E, NoResponseException,
|
||||||
NotConnectedException, InterruptedException {
|
NotConnectedException, InterruptedException, SmackWrappedException {
|
||||||
sendAndWaitForResponse(request);
|
sendAndWaitForResponse(request);
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case Failure:
|
case Failure:
|
||||||
if (failureException != null) {
|
throwException();
|
||||||
throw failureException;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Success, do nothing
|
// Success, do nothing
|
||||||
|
@ -123,11 +125,12 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
* @throws NoResponseException if there was no response marking the synchronization point as success or failed.
|
* @throws NoResponseException if there was no response marking the synchronization point as success or failed.
|
||||||
* @throws E if there was a failure
|
* @throws E if there was a failure
|
||||||
* @throws InterruptedException if the connection is interrupted.
|
* @throws InterruptedException if the connection is interrupted.
|
||||||
|
* @throws SmackWrappedException in case of a wrapped exception;
|
||||||
*/
|
*/
|
||||||
public void checkIfSuccessOrWaitOrThrow() throws NoResponseException, E, InterruptedException {
|
public void checkIfSuccessOrWaitOrThrow() throws NoResponseException, E, InterruptedException, SmackWrappedException {
|
||||||
checkIfSuccessOrWait();
|
checkIfSuccessOrWait();
|
||||||
if (state == State.Failure) {
|
if (state == State.Failure) {
|
||||||
throw failureException;
|
throwException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +140,7 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
* @return <code>null</code> if synchronization point was successful, or the failure Exception.
|
* @return <code>null</code> if synchronization point was successful, or the failure Exception.
|
||||||
*/
|
*/
|
||||||
public E checkIfSuccessOrWait() throws NoResponseException, InterruptedException {
|
public Exception checkIfSuccessOrWait() throws NoResponseException, InterruptedException {
|
||||||
connectionLock.lock();
|
connectionLock.lock();
|
||||||
try {
|
try {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
|
@ -145,7 +148,7 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
case Success:
|
case Success:
|
||||||
return null;
|
return null;
|
||||||
case Failure:
|
case Failure:
|
||||||
return failureException;
|
return getException();
|
||||||
default:
|
default:
|
||||||
// Do nothing
|
// Do nothing
|
||||||
break;
|
break;
|
||||||
|
@ -198,6 +201,24 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report this synchronization point as failed because of the given exception. The {@code failureException} must be set.
|
||||||
|
*
|
||||||
|
* @param exception the exception causing this synchronization point to fail.
|
||||||
|
*/
|
||||||
|
public void reportGenericFailure(SmackWrappedException exception) {
|
||||||
|
assert exception != null;
|
||||||
|
connectionLock.lock();
|
||||||
|
try {
|
||||||
|
state = State.Failure;
|
||||||
|
this.smackWrappedExcpetion = exception;
|
||||||
|
condition.signalAll();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
connectionLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this synchronization point was successful.
|
* Check if this synchronization point was successful.
|
||||||
*
|
*
|
||||||
|
@ -213,6 +234,16 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isNotInInitialState() {
|
||||||
|
connectionLock.lock();
|
||||||
|
try {
|
||||||
|
return state != State.Initial;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
connectionLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this synchronization point has its request already sent.
|
* Check if this synchronization point has its request already sent.
|
||||||
*
|
*
|
||||||
|
@ -256,6 +287,20 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Exception getException() {
|
||||||
|
if (failureException != null) {
|
||||||
|
return failureException;
|
||||||
|
}
|
||||||
|
return smackWrappedExcpetion;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throwException() throws E, SmackWrappedException {
|
||||||
|
if (failureException != null) {
|
||||||
|
throw failureException;
|
||||||
|
}
|
||||||
|
throw smackWrappedExcpetion;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for a response and throw a {@link NoResponseException} if there was none.
|
* Check for a response and throw a {@link NoResponseException} if there was none.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -264,7 +309,7 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
* @return <code>true</code> if synchronization point was successful, <code>false</code> on failure.
|
* @return <code>true</code> if synchronization point was successful, <code>false</code> on failure.
|
||||||
* @throws NoResponseException
|
* @throws NoResponseException
|
||||||
*/
|
*/
|
||||||
private E checkForResponse() throws NoResponseException {
|
private Exception checkForResponse() throws NoResponseException {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case Initial:
|
case Initial:
|
||||||
case NoResponse:
|
case NoResponse:
|
||||||
|
@ -273,7 +318,7 @@ public class SynchronizationPoint<E extends Exception> {
|
||||||
case Success:
|
case Success:
|
||||||
return null;
|
return null;
|
||||||
case Failure:
|
case Failure:
|
||||||
return failureException;
|
return getException();
|
||||||
default:
|
default:
|
||||||
throw new AssertionError("Unknown state " + state);
|
throw new AssertionError("Unknown state " + state);
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,6 +198,12 @@ public class StanzaError extends AbstractError implements ExtensionElement {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder("XMPPError: ");
|
StringBuilder sb = new StringBuilder("XMPPError: ");
|
||||||
sb.append(condition.toString()).append(" - ").append(type.toString());
|
sb.append(condition.toString()).append(" - ").append(type.toString());
|
||||||
|
|
||||||
|
String descriptiveText = getDescriptiveText();
|
||||||
|
if (descriptiveText != null) {
|
||||||
|
sb.append(" [").append(descriptiveText).append(']');
|
||||||
|
}
|
||||||
|
|
||||||
if (errorGenerator != null) {
|
if (errorGenerator != null) {
|
||||||
sb.append(". Generated by ").append(errorGenerator);
|
sb.append(". Generated by ").append(errorGenerator);
|
||||||
}
|
}
|
||||||
|
@ -385,11 +391,6 @@ public class StanzaError extends AbstractError implements ExtensionElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Condition fromString(String string) {
|
public static Condition fromString(String string) {
|
||||||
// Backwards compatibility for older implementations still using RFC 3920. RFC 6120
|
|
||||||
// changed 'xml-not-well-formed' to 'not-well-formed'.
|
|
||||||
if ("xml-not-well-formed".equals(string)) {
|
|
||||||
string = "not-well-formed";
|
|
||||||
}
|
|
||||||
string = string.replace('-', '_');
|
string = string.replace('-', '_');
|
||||||
Condition condition = null;
|
Condition condition = null;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -186,6 +186,11 @@ public class StreamError extends AbstractError implements Nonza {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Condition fromString(String string) {
|
public static Condition fromString(String string) {
|
||||||
|
// Backwards compatibility for older implementations still using RFC 3920. RFC 6120
|
||||||
|
// changed 'xml-not-well-formed' to 'not-well-formed'.
|
||||||
|
if ("xml-not-well-formed".equals(string)) {
|
||||||
|
string = "not-well-formed";
|
||||||
|
}
|
||||||
string = string.replace('-', '_');
|
string = string.replace('-', '_');
|
||||||
Condition condition = null;
|
Condition condition = null;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -123,7 +123,9 @@ public class IntrospectionProvider{
|
||||||
case "java.lang.String":
|
case "java.lang.String":
|
||||||
return value;
|
return value;
|
||||||
case "boolean":
|
case "boolean":
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
return Boolean.valueOf(value);
|
return Boolean.valueOf(value);
|
||||||
|
// CHECKSTYLE:ON
|
||||||
case "int":
|
case "int":
|
||||||
return Integer.valueOf(value);
|
return Integer.valueOf(value);
|
||||||
case "long":
|
case "long":
|
||||||
|
|
|
@ -58,7 +58,7 @@ class HTTPProxySocketConnection implements ProxySocketConnection {
|
||||||
proxyLine = "\r\nProxy-Authorization: Basic " + Base64.encode(username + ":" + password);
|
proxyLine = "\r\nProxy-Authorization: Basic " + Base64.encode(username + ":" + password);
|
||||||
}
|
}
|
||||||
socket.getOutputStream().write((hostport + " HTTP/1.1\r\nHost: "
|
socket.getOutputStream().write((hostport + " HTTP/1.1\r\nHost: "
|
||||||
+ hostport + proxyLine + "\r\n\r\n").getBytes("UTF-8"));
|
+ host + ":" + port + proxyLine + "\r\n\r\n").getBytes("UTF-8"));
|
||||||
|
|
||||||
InputStream in = socket.getInputStream();
|
InputStream in = socket.getInputStream();
|
||||||
StringBuilder got = new StringBuilder(100);
|
StringBuilder got = new StringBuilder(100);
|
||||||
|
@ -115,7 +115,8 @@ class HTTPProxySocketConnection implements ProxySocketConnection {
|
||||||
int code = Integer.parseInt(m.group(1));
|
int code = Integer.parseInt(m.group(1));
|
||||||
|
|
||||||
if (code != HttpURLConnection.HTTP_OK) {
|
if (code != HttpURLConnection.HTTP_OK) {
|
||||||
throw new ProxyException(ProxyInfo.ProxyType.HTTP);
|
throw new ProxyException(ProxyInfo.ProxyType.HTTP,
|
||||||
|
"Error code in proxy response: " + code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,27 @@ public class ParserUtils {
|
||||||
return Resourcepart.from(resourcepartString);
|
return Resourcepart.from(resourcepartString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prase a string to a boolean value as per "xs:boolean". Valid input strings are "true", "1" for true, and "false", "0" for false.
|
||||||
|
*
|
||||||
|
* @param booleanString the input string.
|
||||||
|
* @return the boolean representation of the input string
|
||||||
|
* @throws IllegalArgumentException if the input string is not valid.
|
||||||
|
* @since 4.3.2
|
||||||
|
*/
|
||||||
|
public static boolean parseXmlBoolean(String booleanString) {
|
||||||
|
switch (booleanString) {
|
||||||
|
case "true":
|
||||||
|
case "1":
|
||||||
|
return true;
|
||||||
|
case "false":
|
||||||
|
case "0":
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(booleanString + " is not a valid boolean string");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the boolean value of an argument.
|
* Get the boolean value of an argument.
|
||||||
*
|
*
|
||||||
|
@ -141,7 +162,7 @@ public class ParserUtils {
|
||||||
if (valueString == null)
|
if (valueString == null)
|
||||||
return null;
|
return null;
|
||||||
valueString = valueString.toLowerCase(Locale.US);
|
valueString = valueString.toLowerCase(Locale.US);
|
||||||
return valueString.equals("true") || valueString.equals("0");
|
return parseXmlBoolean(valueString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getBooleanAttribute(XmlPullParser parser, String name,
|
public static boolean getBooleanAttribute(XmlPullParser parser, String name,
|
||||||
|
|
|
@ -35,7 +35,11 @@ public class XmlUtil {
|
||||||
private static final TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
private static final TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
transformerFactory.setAttribute("indent-number", 2);
|
try {
|
||||||
|
transformerFactory.setAttribute("indent-number", 2);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
LOGGER.log(Level.INFO, "XML TransformerFactory does not support indent-number attribute", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String prettyFormatXml(CharSequence xml) {
|
public static String prettyFormatXml(CharSequence xml) {
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2018 Florian Schmaus.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.jxmpp.jid.JidTestUtil;
|
||||||
|
|
||||||
|
public class ConnectionConfigurationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setIp() {
|
||||||
|
DummyConnectionConfiguration.Builder builder = newUnitTestBuilder();
|
||||||
|
|
||||||
|
final String ip = "192.168.0.1";
|
||||||
|
builder.setHostAddressByNameOrIp(ip);
|
||||||
|
|
||||||
|
DummyConnectionConfiguration connectionConfiguration = builder.build();
|
||||||
|
assertEquals('/' + ip, connectionConfiguration.getHostAddress().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setFqdn() {
|
||||||
|
DummyConnectionConfiguration.Builder builder = newUnitTestBuilder();
|
||||||
|
|
||||||
|
final String fqdn = "foo.example.org";
|
||||||
|
builder.setHostAddressByNameOrIp(fqdn);
|
||||||
|
|
||||||
|
DummyConnectionConfiguration connectionConfiguration = builder.build();
|
||||||
|
assertEquals(fqdn, connectionConfiguration.getHost().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DummyConnectionConfiguration.Builder newUnitTestBuilder() {
|
||||||
|
DummyConnectionConfiguration.Builder builder = DummyConnectionConfiguration.builder();
|
||||||
|
builder.setXmppDomain(JidTestUtil.DOMAIN_BARE_JID_1);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DummyConnectionConfiguration extends ConnectionConfiguration {
|
||||||
|
|
||||||
|
protected DummyConnectionConfiguration(Builder builder) {
|
||||||
|
super(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Builder
|
||||||
|
extends ConnectionConfiguration.Builder<Builder, DummyConnectionConfiguration> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DummyConnectionConfiguration build() {
|
||||||
|
return new DummyConnectionConfiguration(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Builder getThis() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -100,6 +100,11 @@ public class DummyConnection extends AbstractXMPPConnection {
|
||||||
callConnectionClosedListener();
|
callConnectionClosedListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void instantShutdown() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSecureConnection() {
|
public boolean isSecureConnection() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -226,4 +231,5 @@ public class DummyConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,4 +104,22 @@ public class StreamErrorTest {
|
||||||
assertNotNull(appSpecificElement);
|
assertNotNull(appSpecificElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStreamErrorXmlNotWellFormed() {
|
||||||
|
StreamError error = null;
|
||||||
|
final String xml =
|
||||||
|
// Usually the stream:stream element has more attributes (to, version, ...)
|
||||||
|
// We omit those, since they are not relevant for testing
|
||||||
|
"<stream:stream from='im.example.com' id='++TR84Sm6A3hnt3Q065SnAbbk3Y=' xmlns:stream='http://etherx.jabber.org/streams'>" +
|
||||||
|
"<stream:error><xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error>" +
|
||||||
|
"</stream:stream>";
|
||||||
|
try {
|
||||||
|
XmlPullParser parser = PacketParserUtils.getParserFor(xml, "error");
|
||||||
|
error = PacketParserUtils.parseStreamError(parser);
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail(e.getMessage());
|
||||||
|
}
|
||||||
|
assertNotNull(error);
|
||||||
|
assertEquals(Condition.not_well_formed, error.getCondition());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.hoxt.provider;
|
package org.jivesoftware.smackx.hoxt.provider;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.ParserUtils;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.hoxt.packet.HttpMethod;
|
import org.jivesoftware.smackx.hoxt.packet.HttpMethod;
|
||||||
import org.jivesoftware.smackx.hoxt.packet.HttpOverXmppReq;
|
import org.jivesoftware.smackx.hoxt.packet.HttpOverXmppReq;
|
||||||
|
|
||||||
|
@ -47,13 +49,13 @@ public class HttpOverXmppReqProvider extends AbstractHttpOverXmppProvider<HttpOv
|
||||||
String jingleStr = parser.getAttributeValue("", AbstractHttpOverXmppProvider.ELEMENT_JINGLE);
|
String jingleStr = parser.getAttributeValue("", AbstractHttpOverXmppProvider.ELEMENT_JINGLE);
|
||||||
|
|
||||||
if (sipubStr != null) {
|
if (sipubStr != null) {
|
||||||
builder.setSipub(Boolean.valueOf(sipubStr));
|
builder.setSipub(ParserUtils.parseXmlBoolean(sipubStr));
|
||||||
}
|
}
|
||||||
if (ibbStr != null) {
|
if (ibbStr != null) {
|
||||||
builder.setIbb(Boolean.valueOf(ibbStr));
|
builder.setIbb(ParserUtils.parseXmlBoolean(ibbStr));
|
||||||
}
|
}
|
||||||
if (jingleStr != null) {
|
if (jingleStr != null) {
|
||||||
builder.setJingle(Boolean.valueOf(jingleStr));
|
builder.setJingle(ParserUtils.parseXmlBoolean(jingleStr));
|
||||||
}
|
}
|
||||||
|
|
||||||
String maxChunkSize = parser.getAttributeValue("", ATTRIBUTE_MAX_CHUNK_SIZE);
|
String maxChunkSize = parser.getAttributeValue("", ATTRIBUTE_MAX_CHUNK_SIZE);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2016 Florian Schmaus
|
* Copyright © 2016-2018 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.iot.control.element;
|
package org.jivesoftware.smackx.iot.control.element;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.ParserUtils;
|
||||||
|
|
||||||
public class SetBoolData extends SetData {
|
public class SetBoolData extends SetData {
|
||||||
|
|
||||||
public SetBoolData(String name, boolean value) {
|
public SetBoolData(String name, boolean value) {
|
||||||
|
@ -31,7 +33,7 @@ public class SetBoolData extends SetData {
|
||||||
|
|
||||||
public Boolean getBooleanValue() {
|
public Boolean getBooleanValue() {
|
||||||
if (booleanCache != null) {
|
if (booleanCache != null) {
|
||||||
booleanCache = Boolean.valueOf(getValue());
|
booleanCache = ParserUtils.parseXmlBoolean(getValue());
|
||||||
}
|
}
|
||||||
return booleanCache;
|
return booleanCache;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ public final class JingleFileTransferManager extends Manager {
|
||||||
super(connection);
|
super(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JingleFileTransferManager getInstanceFor(XMPPConnection connection) {
|
public static synchronized JingleFileTransferManager getInstanceFor(XMPPConnection connection) {
|
||||||
JingleFileTransferManager manager = INSTANCES.get(connection);
|
JingleFileTransferManager manager = INSTANCES.get(connection);
|
||||||
if (manager == null) {
|
if (manager == null) {
|
||||||
manager = new JingleFileTransferManager(connection);
|
manager = new JingleFileTransferManager(connection);
|
||||||
|
|
|
@ -114,8 +114,8 @@ import org.jxmpp.jid.Jid;
|
||||||
* <pre>
|
* <pre>
|
||||||
* {@code
|
* {@code
|
||||||
* MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
|
* MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
|
||||||
* .withJid(jid)
|
* .limitResultsToJid(jid)
|
||||||
* .setResultPageSize(10)
|
* .setResultPageSizeTo(10)
|
||||||
* .queryLastPage()
|
* .queryLastPage()
|
||||||
* .build();
|
* .build();
|
||||||
* MamQuery mamQuery = mamManager.queryArchive(mamQueryArgs);
|
* MamQuery mamQuery = mamManager.queryArchive(mamQueryArgs);
|
||||||
|
@ -178,7 +178,9 @@ public final class MamManager extends Manager {
|
||||||
* @param connection the XMPP connection to get the archive for.
|
* @param connection the XMPP connection to get the archive for.
|
||||||
* @return the instance of MamManager.
|
* @return the instance of MamManager.
|
||||||
*/
|
*/
|
||||||
|
// CHECKSTYLE:OFF:RegexpSingleline
|
||||||
public static MamManager getInstanceFor(XMPPConnection connection) {
|
public static MamManager getInstanceFor(XMPPConnection connection) {
|
||||||
|
// CHECKSTYLE:ON:RegexpSingleline
|
||||||
return getInstanceFor(connection, (Jid) null);
|
return getInstanceFor(connection, (Jid) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -995,7 +997,7 @@ public final class MamManager extends Manager {
|
||||||
public List<Message> pagePrevious(int count) throws NoResponseException, XMPPErrorException,
|
public List<Message> pagePrevious(int count) throws NoResponseException, XMPPErrorException,
|
||||||
NotConnectedException, NotLoggedInException, InterruptedException {
|
NotConnectedException, NotLoggedInException, InterruptedException {
|
||||||
RSMSet previousResultRsmSet = getPreviousRsmSet();
|
RSMSet previousResultRsmSet = getPreviousRsmSet();
|
||||||
RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getLast(), RSMSet.PageDirection.before);
|
RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getFirst(), RSMSet.PageDirection.before);
|
||||||
return page(requestRsmSet);
|
return page(requestRsmSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ public final class ReferenceManager extends Manager {
|
||||||
* @param connection xmpp connection
|
* @param connection xmpp connection
|
||||||
* @return reference manager instance
|
* @return reference manager instance
|
||||||
*/
|
*/
|
||||||
public static ReferenceManager getInstanceFor(XMPPConnection connection) {
|
public static synchronized ReferenceManager getInstanceFor(XMPPConnection connection) {
|
||||||
ReferenceManager manager = INSTANCES.get(connection);
|
ReferenceManager manager = INSTANCES.get(connection);
|
||||||
if (manager == null) {
|
if (manager == null) {
|
||||||
manager = new ReferenceManager(connection);
|
manager = new ReferenceManager(connection);
|
||||||
|
|
|
@ -27,7 +27,6 @@ import org.jivesoftware.smack.XMPPConnectionRegistry;
|
||||||
import org.jivesoftware.smack.filter.AndFilter;
|
import org.jivesoftware.smack.filter.AndFilter;
|
||||||
import org.jivesoftware.smack.filter.MessageTypeFilter;
|
import org.jivesoftware.smack.filter.MessageTypeFilter;
|
||||||
import org.jivesoftware.smack.filter.NotFilter;
|
import org.jivesoftware.smack.filter.NotFilter;
|
||||||
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
|
|
||||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||||
import org.jivesoftware.smack.filter.ToTypeFilter;
|
import org.jivesoftware.smack.filter.ToTypeFilter;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
|
@ -46,13 +45,12 @@ public final class StableUniqueStanzaIdManager extends Manager {
|
||||||
MessageTypeFilter.NORMAL_OR_CHAT_OR_HEADLINE,
|
MessageTypeFilter.NORMAL_OR_CHAT_OR_HEADLINE,
|
||||||
ToTypeFilter.ENTITY_FULL_OR_BARE_JID);
|
ToTypeFilter.ENTITY_FULL_OR_BARE_JID);
|
||||||
|
|
||||||
private static final StanzaFilter ORIGIN_ID_FILTER = new StanzaExtensionFilter(OriginIdElement.ELEMENT, NAMESPACE);
|
|
||||||
|
|
||||||
// Listener for outgoing stanzas that adds origin-ids to outgoing stanzas.
|
// Listener for outgoing stanzas that adds origin-ids to outgoing stanzas.
|
||||||
private final StanzaListener stanzaListener = new StanzaListener() {
|
private static final StanzaListener ADD_ORIGIN_ID_INTERCEPTOR = new StanzaListener() {
|
||||||
@Override
|
@Override
|
||||||
public void processStanza(Stanza stanza) {
|
public void processStanza(Stanza stanza) {
|
||||||
OriginIdElement.addOriginId((Message) stanza);
|
Message message = (Message) stanza;
|
||||||
|
OriginIdElement.addOriginId(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,7 +78,7 @@ public final class StableUniqueStanzaIdManager extends Manager {
|
||||||
* @param connection xmpp-connection
|
* @param connection xmpp-connection
|
||||||
* @return manager instance for the connection
|
* @return manager instance for the connection
|
||||||
*/
|
*/
|
||||||
public static StableUniqueStanzaIdManager getInstanceFor(XMPPConnection connection) {
|
public static synchronized StableUniqueStanzaIdManager getInstanceFor(XMPPConnection connection) {
|
||||||
StableUniqueStanzaIdManager manager = INSTANCES.get(connection);
|
StableUniqueStanzaIdManager manager = INSTANCES.get(connection);
|
||||||
if (manager == null) {
|
if (manager == null) {
|
||||||
manager = new StableUniqueStanzaIdManager(connection);
|
manager = new StableUniqueStanzaIdManager(connection);
|
||||||
|
@ -95,7 +93,7 @@ public final class StableUniqueStanzaIdManager extends Manager {
|
||||||
public synchronized void enable() {
|
public synchronized void enable() {
|
||||||
ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE);
|
ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE);
|
||||||
StanzaFilter filter = new AndFilter(OUTGOING_FILTER, new NotFilter(OUTGOING_FILTER));
|
StanzaFilter filter = new AndFilter(OUTGOING_FILTER, new NotFilter(OUTGOING_FILTER));
|
||||||
connection().addStanzaInterceptor(stanzaListener, filter);
|
connection().addStanzaInterceptor(ADD_ORIGIN_ID_INTERCEPTOR, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,7 +101,7 @@ public final class StableUniqueStanzaIdManager extends Manager {
|
||||||
*/
|
*/
|
||||||
public synchronized void disable() {
|
public synchronized void disable() {
|
||||||
ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE);
|
ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE);
|
||||||
connection().removeStanzaInterceptor(stanzaListener);
|
connection().removeStanzaInterceptor(ADD_ORIGIN_ID_INTERCEPTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,7 +23,11 @@ import org.xmlpull.v1.XmlPullParser;
|
||||||
|
|
||||||
public class OriginIdProvider extends ExtensionElementProvider<OriginIdElement> {
|
public class OriginIdProvider extends ExtensionElementProvider<OriginIdElement> {
|
||||||
|
|
||||||
public static final OriginIdProvider TEST_INSTANCE = new OriginIdProvider();
|
public static final OriginIdProvider INSTANCE = new OriginIdProvider();
|
||||||
|
|
||||||
|
// TODO: Remove in Smack 4.4.
|
||||||
|
@Deprecated
|
||||||
|
public static final OriginIdProvider TEST_INSTANCE = INSTANCE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OriginIdElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
public OriginIdElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||||
|
|
|
@ -23,7 +23,11 @@ import org.xmlpull.v1.XmlPullParser;
|
||||||
|
|
||||||
public class StanzaIdProvider extends ExtensionElementProvider<StanzaIdElement> {
|
public class StanzaIdProvider extends ExtensionElementProvider<StanzaIdElement> {
|
||||||
|
|
||||||
public static StanzaIdProvider TEST_INSTANCE = new StanzaIdProvider();
|
public static final StanzaIdProvider INSTANCE = new StanzaIdProvider();
|
||||||
|
|
||||||
|
// TODO: Remove in Smack 4.4.
|
||||||
|
@Deprecated
|
||||||
|
public static final StanzaIdProvider TEST_INSTANCE = INSTANCE;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StanzaIdElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
public StanzaIdElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||||
|
|
|
@ -61,7 +61,7 @@ public final class SpoilerManager extends Manager {
|
||||||
* @param connection xmpp connection
|
* @param connection xmpp connection
|
||||||
* @return SpoilerManager
|
* @return SpoilerManager
|
||||||
*/
|
*/
|
||||||
public static SpoilerManager getInstanceFor(XMPPConnection connection) {
|
public static synchronized SpoilerManager getInstanceFor(XMPPConnection connection) {
|
||||||
SpoilerManager manager = INSTANCES.get(connection);
|
SpoilerManager manager = INSTANCES.get(connection);
|
||||||
if (manager == null) {
|
if (manager == null) {
|
||||||
manager = new SpoilerManager(connection);
|
manager = new SpoilerManager(connection);
|
||||||
|
|
|
@ -25,6 +25,7 @@ import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.test.util.SmackTestSuite;
|
import org.jivesoftware.smack.test.util.SmackTestSuite;
|
||||||
import org.jivesoftware.smack.test.util.TestUtils;
|
import org.jivesoftware.smack.test.util.TestUtils;
|
||||||
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jivesoftware.smackx.sid.element.OriginIdElement;
|
import org.jivesoftware.smackx.sid.element.OriginIdElement;
|
||||||
import org.jivesoftware.smackx.sid.element.StanzaIdElement;
|
import org.jivesoftware.smackx.sid.element.StanzaIdElement;
|
||||||
import org.jivesoftware.smackx.sid.provider.OriginIdProvider;
|
import org.jivesoftware.smackx.sid.provider.OriginIdProvider;
|
||||||
|
@ -42,7 +43,7 @@ public class StableUniqueStanzaIdTest extends SmackTestSuite {
|
||||||
assertEquals("alice@wonderland.lit", element.getBy());
|
assertEquals("alice@wonderland.lit", element.getBy());
|
||||||
assertXMLEqual(xml, element.toXML(null).toString());
|
assertXMLEqual(xml, element.toXML(null).toString());
|
||||||
|
|
||||||
StanzaIdElement parsed = StanzaIdProvider.TEST_INSTANCE.parse(TestUtils.getParser(xml));
|
StanzaIdElement parsed = StanzaIdProvider.INSTANCE.parse(TestUtils.getParser(xml));
|
||||||
assertEquals(element.getId(), parsed.getId());
|
assertEquals(element.getId(), parsed.getId());
|
||||||
assertEquals(element.getBy(), parsed.getBy());
|
assertEquals(element.getBy(), parsed.getBy());
|
||||||
}
|
}
|
||||||
|
@ -54,7 +55,7 @@ public class StableUniqueStanzaIdTest extends SmackTestSuite {
|
||||||
assertEquals("de305d54-75b4-431b-adb2-eb6b9e546013", element.getId());
|
assertEquals("de305d54-75b4-431b-adb2-eb6b9e546013", element.getId());
|
||||||
assertXMLEqual(xml, element.toXML(null).toString());
|
assertXMLEqual(xml, element.toXML(null).toString());
|
||||||
|
|
||||||
OriginIdElement parsed = OriginIdProvider.TEST_INSTANCE.parse(TestUtils.getParser(xml));
|
OriginIdElement parsed = OriginIdProvider.INSTANCE.parse(TestUtils.getParser(xml));
|
||||||
assertEquals(element.getId(), parsed.getId());
|
assertEquals(element.getId(), parsed.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,4 +82,17 @@ public class StableUniqueStanzaIdTest extends SmackTestSuite {
|
||||||
assertTrue(StanzaIdElement.hasStanzaId(message));
|
assertTrue(StanzaIdElement.hasStanzaId(message));
|
||||||
assertEquals(stanzaId, StanzaIdElement.getStanzaId(message));
|
assertEquals(stanzaId, StanzaIdElement.getStanzaId(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultipleUssidExtensions() throws Exception {
|
||||||
|
String message = "<message xmlns='jabber:client' from='e4aec989-3e20-4846-83bf-f50df89b5d07@muclight.example.com/user1@example.com' to='user1@example.com' id='6b71fe3a-3cb2-489c-9c8e-b6879761d15e' type='groupchat'>" +
|
||||||
|
"<body>Test message</body>" +
|
||||||
|
"<markable xmlns='urn:xmpp:chat-markers:0'/>" +
|
||||||
|
"<stanza-id by='e4aec989-3e20-4846-83bf-f50df89b5d07@muclight.example.com' id='B0KK24ETVC81' xmlns='urn:xmpp:sid:0'/>" +
|
||||||
|
"<stanza-id by='user1@example.com' id='B0KK24EV89G1' xmlns='urn:xmpp:sid:0'/>" +
|
||||||
|
"</message>";
|
||||||
|
Message messageStanza = PacketParserUtils.parseStanza(message);
|
||||||
|
|
||||||
|
assertTrue(StanzaIdElement.hasStanzaId(messageStanza));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,12 +269,12 @@ public class Bookmarks implements PrivateData {
|
||||||
|
|
||||||
private static BookmarkedConference getConferenceStorage(XmlPullParser parser) throws XmlPullParserException, IOException {
|
private static BookmarkedConference getConferenceStorage(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||||
String name = parser.getAttributeValue("", "name");
|
String name = parser.getAttributeValue("", "name");
|
||||||
String autojoin = parser.getAttributeValue("", "autojoin");
|
boolean autojoin = ParserUtils.getBooleanAttribute(parser, "autojoin", false);
|
||||||
EntityBareJid jid = ParserUtils.getBareJidAttribute(parser);
|
EntityBareJid jid = ParserUtils.getBareJidAttribute(parser);
|
||||||
|
|
||||||
BookmarkedConference conf = new BookmarkedConference(jid);
|
BookmarkedConference conf = new BookmarkedConference(jid);
|
||||||
conf.setName(name);
|
conf.setName(name);
|
||||||
conf.setAutoJoin(Boolean.valueOf(autojoin));
|
conf.setAutoJoin(autojoin);
|
||||||
|
|
||||||
// Check for nickname
|
// Check for nickname
|
||||||
boolean done = false;
|
boolean done = false;
|
||||||
|
|
|
@ -211,6 +211,7 @@ public final class Socks5Proxy {
|
||||||
if (this.serverSocket != null) {
|
if (this.serverSocket != null) {
|
||||||
this.serverThread = new Thread(this.serverProcess);
|
this.serverThread = new Thread(this.serverProcess);
|
||||||
this.serverThread.setName("Smack Local SOCKS5 Proxy");
|
this.serverThread.setName("Smack Local SOCKS5 Proxy");
|
||||||
|
this.serverThread.setDaemon(true);
|
||||||
this.serverThread.start();
|
this.serverThread.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2003-2007 Jive Software, 2018 Florian Schmaus.
|
* Copyright 2003-2007 Jive Software, 2018-2019 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -227,7 +227,7 @@ public final class ServiceDiscoveryManager extends Manager {
|
||||||
/**
|
/**
|
||||||
* Returns the type of client that will be returned when asked for the client identity in a
|
* Returns the type of client that will be returned when asked for the client identity in a
|
||||||
* disco request. The valid types are defined by the category client. Follow this link to learn
|
* disco request. The valid types are defined by the category client. Follow this link to learn
|
||||||
* the possible types: <a href="http://xmpp.org/registrar/disco-categories.html#client">Jabber::Registrar</a>.
|
* the possible types: <a href="https://xmpp.org/registrar/disco-categories.html">XMPP Registry for Service Discovery Identities</a>
|
||||||
*
|
*
|
||||||
* @return the type of client that will be returned when asked for the client identity in a
|
* @return the type of client that will be returned when asked for the client identity in a
|
||||||
* disco request.
|
* disco request.
|
||||||
|
@ -271,8 +271,8 @@ public final class ServiceDiscoveryManager extends Manager {
|
||||||
*/
|
*/
|
||||||
public Set<DiscoverInfo.Identity> getIdentities() {
|
public Set<DiscoverInfo.Identity> getIdentities() {
|
||||||
Set<Identity> res = new HashSet<>(identities);
|
Set<Identity> res = new HashSet<>(identities);
|
||||||
// Add the default identity that must exist
|
// Add the main identity that must exist
|
||||||
res.add(defaultIdentity);
|
res.add(identity);
|
||||||
return Collections.unmodifiableSet(res);
|
return Collections.unmodifiableSet(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -263,7 +263,7 @@ public class DiscoverInfo extends IQ implements TypedCloneable<DiscoverInfo> {
|
||||||
* Represents the identity of a given XMPP entity. An entity may have many identities but all
|
* Represents the identity of a given XMPP entity. An entity may have many identities but all
|
||||||
* the identities SHOULD have the same name.<p>
|
* the identities SHOULD have the same name.<p>
|
||||||
*
|
*
|
||||||
* Refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
|
* Refer to <a href="https://xmpp.org/registrar/disco-categories.html">XMPP Registry for Service Discovery Identities</a>
|
||||||
* in order to get the official registry of values for the <i>category</i> and <i>type</i>
|
* in order to get the official registry of values for the <i>category</i> and <i>type</i>
|
||||||
* attributes.
|
* attributes.
|
||||||
*
|
*
|
||||||
|
@ -327,7 +327,7 @@ public class DiscoverInfo extends IQ implements TypedCloneable<DiscoverInfo> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the entity's category. To get the official registry of values for the
|
* Returns the entity's category. To get the official registry of values for the
|
||||||
* 'category' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
|
* 'category' attribute refer to <a href="https://xmpp.org/registrar/disco-categories.html">XMPP Registry for Service Discovery Identities</a>.
|
||||||
*
|
*
|
||||||
* @return the entity's category.
|
* @return the entity's category.
|
||||||
*/
|
*/
|
||||||
|
@ -346,7 +346,7 @@ public class DiscoverInfo extends IQ implements TypedCloneable<DiscoverInfo> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the entity's type. To get the official registry of values for the
|
* Returns the entity's type. To get the official registry of values for the
|
||||||
* 'type' attribute refer to <a href="http://www.jabber.org/registrar/disco-categories.html">Jabber::Registrar</a>
|
* 'type' attribute refer to <a href="https://xmpp.org/registrar/disco-categories.html">XMPP Registry for Service Discovery Identities</a>.
|
||||||
*
|
*
|
||||||
* @return the entity's type.
|
* @return the entity's type.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -48,7 +48,7 @@ public final class JingleTransportMethodManager extends Manager {
|
||||||
super(connection);
|
super(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JingleTransportMethodManager getInstanceFor(XMPPConnection connection) {
|
public static synchronized JingleTransportMethodManager getInstanceFor(XMPPConnection connection) {
|
||||||
JingleTransportMethodManager manager = INSTANCES.get(connection);
|
JingleTransportMethodManager manager = INSTANCES.get(connection);
|
||||||
if (manager == null) {
|
if (manager == null) {
|
||||||
manager = new JingleTransportMethodManager(connection);
|
manager = new JingleTransportMethodManager(connection);
|
||||||
|
|
|
@ -38,7 +38,7 @@ public final class JingleIBBTransportManager extends JingleTransportManager<Jing
|
||||||
JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleIBBTransportProvider());
|
JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleIBBTransportProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JingleIBBTransportManager getInstanceFor(XMPPConnection connection) {
|
public static synchronized JingleIBBTransportManager getInstanceFor(XMPPConnection connection) {
|
||||||
JingleIBBTransportManager manager = INSTANCES.get(connection);
|
JingleIBBTransportManager manager = INSTANCES.get(connection);
|
||||||
if (manager == null) {
|
if (manager == null) {
|
||||||
manager = new JingleIBBTransportManager(connection);
|
manager = new JingleIBBTransportManager(connection);
|
||||||
|
|
|
@ -63,7 +63,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager<Jing
|
||||||
JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleS5BTransportProvider());
|
JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleS5BTransportProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JingleS5BTransportManager getInstanceFor(XMPPConnection connection) {
|
public static synchronized JingleS5BTransportManager getInstanceFor(XMPPConnection connection) {
|
||||||
JingleS5BTransportManager manager = INSTANCES.get(connection);
|
JingleS5BTransportManager manager = INSTANCES.get(connection);
|
||||||
if (manager == null) {
|
if (manager == null) {
|
||||||
manager = new JingleS5BTransportManager(connection);
|
manager = new JingleS5BTransportManager(connection);
|
||||||
|
|
|
@ -92,7 +92,9 @@ public class JivePropertiesExtensionProvider extends ExtensionElementProvider<Ji
|
||||||
value = Double.valueOf(valueText);
|
value = Double.valueOf(valueText);
|
||||||
}
|
}
|
||||||
else if ("boolean".equals(type)) {
|
else if ("boolean".equals(type)) {
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
value = Boolean.valueOf(valueText);
|
value = Boolean.valueOf(valueText);
|
||||||
|
// CHECKSTYLE:ON
|
||||||
}
|
}
|
||||||
else if ("string".equals(type)) {
|
else if ("string".equals(type)) {
|
||||||
value = valueText;
|
value = valueText;
|
||||||
|
|
|
@ -205,7 +205,6 @@ public class MultiUserChat {
|
||||||
final Presence presence = (Presence) packet;
|
final Presence presence = (Presence) packet;
|
||||||
final EntityFullJid from = presence.getFrom().asEntityFullJidIfPossible();
|
final EntityFullJid from = presence.getFrom().asEntityFullJidIfPossible();
|
||||||
if (from == null) {
|
if (from == null) {
|
||||||
LOGGER.warning("Presence not from a full JID: " + presence.getFrom());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final EntityFullJid myRoomJID = myRoomJid;
|
final EntityFullJid myRoomJID = myRoomJid;
|
||||||
|
@ -341,8 +340,9 @@ public class MultiUserChat {
|
||||||
|
|
||||||
// Setup the messageListeners and presenceListeners *before* the join presence is send.
|
// Setup the messageListeners and presenceListeners *before* the join presence is send.
|
||||||
connection.addSyncStanzaListener(messageListener, fromRoomGroupchatFilter);
|
connection.addSyncStanzaListener(messageListener, fromRoomGroupchatFilter);
|
||||||
connection.addSyncStanzaListener(presenceListener, new AndFilter(fromRoomFilter,
|
StanzaFilter presenceFromRoomFilter = new AndFilter(fromRoomFilter,
|
||||||
StanzaTypeFilter.PRESENCE));
|
StanzaTypeFilter.PRESENCE);
|
||||||
|
connection.addSyncStanzaListener(presenceListener, presenceFromRoomFilter);
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
connection.addSyncStanzaListener(subjectListener,
|
connection.addSyncStanzaListener(subjectListener,
|
||||||
new AndFilter(fromRoomFilter,
|
new AndFilter(fromRoomFilter,
|
||||||
|
@ -371,15 +371,27 @@ public class MultiUserChat {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
StanzaCollector presenceStanzaCollector = null;
|
||||||
Presence presence;
|
Presence presence;
|
||||||
try {
|
try {
|
||||||
presence = connection.createStanzaCollectorAndSend(responseFilter, joinPresence).nextResultOrThrow(conf.getTimeout());
|
// This stanza collector will collect the final self presence from the MUC, which also signals that we have successful entered the MUC.
|
||||||
|
StanzaCollector selfPresenceCollector = connection.createStanzaCollectorAndSend(responseFilter, joinPresence);
|
||||||
|
StanzaCollector.Configuration presenceStanzaCollectorConfguration = StanzaCollector.newConfiguration().setCollectorToReset(
|
||||||
|
selfPresenceCollector).setStanzaFilter(presenceFromRoomFilter);
|
||||||
|
// This stanza collector is used to reset the timeout of the selfPresenceCollector.
|
||||||
|
presenceStanzaCollector = connection.createStanzaCollector(presenceStanzaCollectorConfguration);
|
||||||
|
presence = selfPresenceCollector.nextResultOrThrow(conf.getTimeout());
|
||||||
}
|
}
|
||||||
catch (NotConnectedException | InterruptedException | NoResponseException | XMPPErrorException e) {
|
catch (NotConnectedException | InterruptedException | NoResponseException | XMPPErrorException e) {
|
||||||
// Ensure that all callbacks are removed if there is an exception
|
// Ensure that all callbacks are removed if there is an exception
|
||||||
removeConnectionCallbacks();
|
removeConnectionCallbacks();
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
if (presenceStanzaCollector != null) {
|
||||||
|
presenceStanzaCollector.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This presence must be send from a full JID. We use the resourcepart of this JID as nick, since the room may
|
// This presence must be send from a full JID. We use the resourcepart of this JID as nick, since the room may
|
||||||
// performed roomnick rewriting
|
// performed roomnick rewriting
|
||||||
|
@ -735,7 +747,12 @@ public class MultiUserChat {
|
||||||
// If we've already joined the room, leave it before joining under a new
|
// If we've already joined the room, leave it before joining under a new
|
||||||
// nickname.
|
// nickname.
|
||||||
if (joined) {
|
if (joined) {
|
||||||
leave();
|
try {
|
||||||
|
leaveSync();
|
||||||
|
}
|
||||||
|
catch (XMPPErrorException | NoResponseException | MucNotJoinedException e) {
|
||||||
|
LOGGER.log(Level.WARNING, "Could not leave MUC prior joining, assuming we are not joined", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
enter(mucEnterConfiguration);
|
enter(mucEnterConfiguration);
|
||||||
}
|
}
|
||||||
|
@ -776,6 +793,50 @@ public class MultiUserChat {
|
||||||
connection.sendStanza(leavePresence);
|
connection.sendStanza(leavePresence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leave the chat room.
|
||||||
|
*
|
||||||
|
* @return the leave presence as reflected by the MUC.
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
* @throws NoResponseException
|
||||||
|
* @throws MucNotJoinedException
|
||||||
|
*/
|
||||||
|
public synchronized Presence leaveSync()
|
||||||
|
throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException, MucNotJoinedException {
|
||||||
|
// Note that this method is intentionally not guarded by
|
||||||
|
// "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.
|
||||||
|
|
||||||
|
// Reset occupant information first so that we are assume that we left the room even if sendStanza() would
|
||||||
|
// throw.
|
||||||
|
userHasLeft();
|
||||||
|
|
||||||
|
final EntityFullJid myRoomJid = this.myRoomJid;
|
||||||
|
if (myRoomJid == null) {
|
||||||
|
throw new MucNotJoinedException(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We leave a room by sending a presence packet where the "to"
|
||||||
|
// field is in the form "roomName@service/nickname"
|
||||||
|
Presence leavePresence = new Presence(Presence.Type.unavailable);
|
||||||
|
leavePresence.setTo(myRoomJid);
|
||||||
|
|
||||||
|
StanzaFilter reflectedLeavePresenceFilter = new AndFilter(
|
||||||
|
StanzaTypeFilter.PRESENCE,
|
||||||
|
new StanzaIdFilter(leavePresence),
|
||||||
|
new OrFilter(
|
||||||
|
new AndFilter(FromMatchesFilter.createFull(myRoomJid), PresenceTypeFilter.UNAVAILABLE, MUCUserStatusCodeFilter.STATUS_110_PRESENCE_TO_SELF),
|
||||||
|
new AndFilter(fromRoomFilter, PresenceTypeFilter.ERROR)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Presence reflectedLeavePresence = connection.createStanzaCollectorAndSend(reflectedLeavePresenceFilter, leavePresence).nextResultOrThrow();
|
||||||
|
|
||||||
|
return reflectedLeavePresence;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a {@link MucConfigFormManager} to configure this room.
|
* Get a {@link MucConfigFormManager} to configure this room.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.jivesoftware.smackx.muc;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -392,19 +393,46 @@ public final class MultiUserChatManager extends Manager {
|
||||||
* @throws NotConnectedException
|
* @throws NotConnectedException
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
* @throws NotAMucServiceException
|
* @throws NotAMucServiceException
|
||||||
|
* @deprecated use {@link #getRoomsHostedBy(DomainBareJid)} instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
|
// TODO: Remove in Smack 4.4.
|
||||||
public List<HostedRoom> getHostedRooms(DomainBareJid serviceName) throws NoResponseException, XMPPErrorException,
|
public List<HostedRoom> getHostedRooms(DomainBareJid serviceName) throws NoResponseException, XMPPErrorException,
|
||||||
NotConnectedException, InterruptedException, NotAMucServiceException {
|
NotConnectedException, InterruptedException, NotAMucServiceException {
|
||||||
|
Map<EntityBareJid, HostedRoom> hostedRooms = getRoomsHostedBy(serviceName);
|
||||||
|
return new ArrayList<>(hostedRooms.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Map of HostedRooms where each HostedRoom has the XMPP address of the room and the room's name.
|
||||||
|
* Once discovered the rooms hosted by a chat service it is possible to discover more detailed room information or
|
||||||
|
* join the room.
|
||||||
|
*
|
||||||
|
* @param serviceName the service that is hosting the rooms to discover.
|
||||||
|
* @return a map from the room's address to its HostedRoom information.
|
||||||
|
* @throws XMPPErrorException
|
||||||
|
* @throws NoResponseException
|
||||||
|
* @throws NotConnectedException
|
||||||
|
* @throws InterruptedException
|
||||||
|
* @throws NotAMucServiceException
|
||||||
|
* @since 4.3.1
|
||||||
|
*/
|
||||||
|
public Map<EntityBareJid, HostedRoom> getRoomsHostedBy(DomainBareJid serviceName) throws NoResponseException, XMPPErrorException,
|
||||||
|
NotConnectedException, InterruptedException, NotAMucServiceException {
|
||||||
if (!providesMucService(serviceName)) {
|
if (!providesMucService(serviceName)) {
|
||||||
throw new NotAMucServiceException(serviceName);
|
throw new NotAMucServiceException(serviceName);
|
||||||
}
|
}
|
||||||
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection());
|
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection());
|
||||||
DiscoverItems discoverItems = discoManager.discoverItems(serviceName);
|
DiscoverItems discoverItems = discoManager.discoverItems(serviceName);
|
||||||
List<DiscoverItems.Item> items = discoverItems.getItems();
|
List<DiscoverItems.Item> items = discoverItems.getItems();
|
||||||
List<HostedRoom> answer = new ArrayList<HostedRoom>(items.size());
|
|
||||||
|
Map<EntityBareJid, HostedRoom> answer = new HashMap<>(items.size());
|
||||||
for (DiscoverItems.Item item : items) {
|
for (DiscoverItems.Item item : items) {
|
||||||
answer.add(new HostedRoom(item));
|
HostedRoom hostedRoom = new HostedRoom(item);
|
||||||
|
HostedRoom previousRoom = answer.put(hostedRoom.getJid(), hostedRoom);
|
||||||
|
assert previousRoom == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,7 +207,8 @@ public class RoomInfo {
|
||||||
|
|
||||||
FormField subjectmodField = form.getField("muc#roominfo_subjectmod");
|
FormField subjectmodField = form.getField("muc#roominfo_subjectmod");
|
||||||
if (subjectmodField != null && !subjectmodField.getValues().isEmpty()) {
|
if (subjectmodField != null && !subjectmodField.getValues().isEmpty()) {
|
||||||
subjectmod = Boolean.valueOf(subjectmodField.getFirstValue());
|
String firstValue = subjectmodField.getFirstValue();
|
||||||
|
subjectmod = ("true".equals(firstValue) || "1".equals(firstValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
FormField urlField = form.getField("muc#roominfo_logs");
|
FormField urlField = form.getField("muc#roominfo_logs");
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2013-2014 Georg Lukas, 2015 Florian Schmaus
|
* Copyright 2013-2014 Georg Lukas, 2015-2019 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -74,8 +74,16 @@ import org.jxmpp.jid.Jid;
|
||||||
*/
|
*/
|
||||||
public final class DeliveryReceiptManager extends Manager {
|
public final class DeliveryReceiptManager extends Manager {
|
||||||
|
|
||||||
private static final StanzaFilter MESSAGES_WITH_DELIVERY_RECEIPT_REQUEST = new AndFilter(StanzaTypeFilter.MESSAGE,
|
/**
|
||||||
new StanzaExtensionFilter(new DeliveryReceiptRequest()));
|
* Filters all non-error messages with receipt requests.
|
||||||
|
* See <a href="https://xmpp.org/extensions/xep-0184.html#when">XEP-0184 § 5.</a> "A sender could request receipts
|
||||||
|
* on any non-error content message (chat, groupchat, headline, or normal)…"
|
||||||
|
*/
|
||||||
|
private static final StanzaFilter NON_ERROR_GROUPCHAT_MESSAGES_WITH_DELIVERY_RECEIPT_REQUEST = new AndFilter(
|
||||||
|
StanzaTypeFilter.MESSAGE,
|
||||||
|
new StanzaExtensionFilter(new DeliveryReceiptRequest()),
|
||||||
|
new NotFilter(MessageTypeFilter.ERROR));
|
||||||
|
|
||||||
private static final StanzaFilter MESSAGES_WITH_DELIVERY_RECEIPT = new AndFilter(StanzaTypeFilter.MESSAGE,
|
private static final StanzaFilter MESSAGES_WITH_DELIVERY_RECEIPT = new AndFilter(StanzaTypeFilter.MESSAGE,
|
||||||
new StanzaExtensionFilter(DeliveryReceipt.ELEMENT, DeliveryReceipt.NAMESPACE));
|
new StanzaExtensionFilter(DeliveryReceipt.ELEMENT, DeliveryReceipt.NAMESPACE));
|
||||||
|
|
||||||
|
@ -175,7 +183,7 @@ public final class DeliveryReceiptManager extends Manager {
|
||||||
}
|
}
|
||||||
connection.sendStanza(ack);
|
connection.sendStanza(ack);
|
||||||
}
|
}
|
||||||
}, MESSAGES_WITH_DELIVERY_RECEIPT_REQUEST);
|
}, NON_ERROR_GROUPCHAT_MESSAGES_WITH_DELIVERY_RECEIPT_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -261,12 +261,14 @@ public class FormField implements NamedElement {
|
||||||
*/
|
*/
|
||||||
public String getFirstValue() {
|
public String getFirstValue() {
|
||||||
CharSequence firstValue;
|
CharSequence firstValue;
|
||||||
|
|
||||||
synchronized (values) {
|
synchronized (values) {
|
||||||
|
if (values.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
firstValue = values.get(0);
|
firstValue = values.get(0);
|
||||||
}
|
}
|
||||||
if (firstValue == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return firstValue.toString();
|
return firstValue.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -951,8 +951,7 @@ public final class Roster extends Manager {
|
||||||
// This is used in case no available presence is found
|
// This is used in case no available presence is found
|
||||||
Presence unavailable = null;
|
Presence unavailable = null;
|
||||||
|
|
||||||
for (Resourcepart resource : userPresences.keySet()) {
|
for (Presence p : userPresences.values()) {
|
||||||
Presence p = userPresences.get(resource);
|
|
||||||
if (!p.isAvailable()) {
|
if (!p.isAvailable()) {
|
||||||
unavailable = p;
|
unavailable = p;
|
||||||
continue;
|
continue;
|
||||||
|
@ -1472,7 +1471,29 @@ public final class Roster extends Manager {
|
||||||
final Presence presence = (Presence) packet;
|
final Presence presence = (Presence) packet;
|
||||||
final Jid from = presence.getFrom();
|
final Jid from = presence.getFrom();
|
||||||
|
|
||||||
final BareJid key = from != null ? from.asBareJid() : null;
|
final BareJid key;
|
||||||
|
if (from != null) {
|
||||||
|
key = from.asBareJid();
|
||||||
|
} else {
|
||||||
|
XMPPConnection connection = connection();
|
||||||
|
if (connection == null) {
|
||||||
|
LOGGER.finest("Connection was null while trying to handle exotic presence stanza: " + presence);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Assume the presence come "from the users account on the server" since no from was set (RFC 6120 §
|
||||||
|
// 8.1.2.1 4.). Note that getUser() may return null, but should never return null in this case as where
|
||||||
|
// connected.
|
||||||
|
EntityFullJid myJid = connection.getUser();
|
||||||
|
if (myJid == null) {
|
||||||
|
LOGGER.info(
|
||||||
|
"Connection had no local address in Roster's presence listener."
|
||||||
|
+ " Possibly we received a presence without from before being authenticated."
|
||||||
|
+ " Presence: " + presence);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOGGER.info("Exotic presence stanza without from received: " + presence);
|
||||||
|
key = myJid.asBareJid();
|
||||||
|
}
|
||||||
|
|
||||||
asyncButOrdered.performAsyncButOrdered(key, new Runnable() {
|
asyncButOrdered.performAsyncButOrdered(key, new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.muc;
|
package org.jivesoftware.smackx.muc;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jivesoftware.smack.MessageListener;
|
import org.jivesoftware.smack.MessageListener;
|
||||||
|
@ -23,8 +26,12 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.jivesoftware.smackx.muc.MultiUserChat.MucCreateConfigFormHandle;
|
import org.jivesoftware.smackx.muc.MultiUserChat.MucCreateConfigFormHandle;
|
||||||
|
import org.jivesoftware.smackx.muc.MultiUserChatException.MucNotJoinedException;
|
||||||
|
import org.jivesoftware.smackx.muc.MultiUserChatException.NotAMucServiceException;
|
||||||
|
import org.jivesoftware.smackx.muc.packet.MUCUser;
|
||||||
|
|
||||||
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
|
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
|
||||||
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
|
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
|
||||||
|
@ -36,6 +43,7 @@ import org.jxmpp.jid.EntityBareJid;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
import org.jxmpp.jid.parts.Localpart;
|
import org.jxmpp.jid.parts.Localpart;
|
||||||
import org.jxmpp.jid.parts.Resourcepart;
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
|
import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
|
|
||||||
public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
|
public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
|
|
||||||
|
@ -61,6 +69,24 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SmackIntegrationTest
|
||||||
|
public void mucJoinLeaveTest() throws XmppStringprepException, NotAMucServiceException, NoResponseException,
|
||||||
|
XMPPErrorException, NotConnectedException, InterruptedException, MucNotJoinedException {
|
||||||
|
EntityBareJid mucAddress = JidCreate.entityBareFrom(Localpart.from("smack-inttest-join-leave-" + randomString),
|
||||||
|
mucService.getDomain());
|
||||||
|
|
||||||
|
MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress);
|
||||||
|
|
||||||
|
muc.join(Resourcepart.from("nick-one"));
|
||||||
|
|
||||||
|
Presence reflectedLeavePresence = muc.leaveSync();
|
||||||
|
|
||||||
|
MUCUser mucUser = MUCUser.from(reflectedLeavePresence);
|
||||||
|
assertNotNull(mucUser);
|
||||||
|
|
||||||
|
assertTrue(mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110));
|
||||||
|
}
|
||||||
|
|
||||||
@SmackIntegrationTest
|
@SmackIntegrationTest
|
||||||
public void mucTest() throws Exception {
|
public void mucTest() throws Exception {
|
||||||
EntityBareJid mucAddress = JidCreate.entityBareFrom(Localpart.from("smack-inttest-" + randomString), mucService.getDomain());
|
EntityBareJid mucAddress = JidCreate.entityBareFrom(Localpart.from("smack-inttest-" + randomString), mucService.getDomain());
|
||||||
|
|
|
@ -620,7 +620,7 @@ public class JingleSession extends JingleNegotiator implements MediaReceivedList
|
||||||
* A XMPP connection
|
* A XMPP connection
|
||||||
* @return a Jingle session
|
* @return a Jingle session
|
||||||
*/
|
*/
|
||||||
public static JingleSession getInstanceFor(XMPPConnection con) {
|
public static synchronized JingleSession getInstanceFor(XMPPConnection con) {
|
||||||
if (con == null) {
|
if (con == null) {
|
||||||
throw new IllegalArgumentException("XMPPConnection cannot be null");
|
throw new IllegalArgumentException("XMPPConnection cannot be null");
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,9 @@ public class WorkgroupProperties extends IQ {
|
||||||
while (!done) {
|
while (!done) {
|
||||||
int eventType = parser.next();
|
int eventType = parser.next();
|
||||||
if ((eventType == XmlPullParser.START_TAG) && ("authRequired".equals(parser.getName()))) {
|
if ((eventType == XmlPullParser.START_TAG) && ("authRequired".equals(parser.getName()))) {
|
||||||
|
// CHECKSTYLE:OFF
|
||||||
props.setAuthRequired(Boolean.valueOf(parser.nextText()).booleanValue());
|
props.setAuthRequired(Boolean.valueOf(parser.nextText()).booleanValue());
|
||||||
|
// CHECKSTYLE:ON
|
||||||
}
|
}
|
||||||
else if ((eventType == XmlPullParser.START_TAG) && ("email".equals(parser.getName()))) {
|
else if ((eventType == XmlPullParser.START_TAG) && ("email".equals(parser.getName()))) {
|
||||||
props.setEmail(parser.nextText());
|
props.setEmail(parser.nextText());
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2013-2018 Florian Schmaus
|
* Copyright 2013-2019 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -41,6 +41,7 @@ import org.minidns.dnsname.DnsName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A DNS resolver (mostly for SRV records), which makes use of the API provided in the javax.* namespace.
|
* A DNS resolver (mostly for SRV records), which makes use of the API provided in the javax.* namespace.
|
||||||
|
* Note that using JavaxResovler requires applications using newer Java versions (at least 11) to declare a dependency on the "sun.jdk" module.
|
||||||
*
|
*
|
||||||
* @author Florian Schmaus
|
* @author Florian Schmaus
|
||||||
*
|
*
|
||||||
|
@ -55,8 +56,8 @@ public class JavaxResolver extends DNSResolver implements SmackInitializer {
|
||||||
Hashtable<String, String> env = new Hashtable<>();
|
Hashtable<String, String> env = new Hashtable<>();
|
||||||
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
|
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
|
||||||
dirContext = new InitialDirContext(env);
|
dirContext = new InitialDirContext(env);
|
||||||
} catch (Exception e) {
|
} catch (NamingException e) {
|
||||||
// Ignore.
|
LOGGER.log(Level.SEVERE, "Could not construct InitialDirContext", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to set this DNS resolver as primary one
|
// Try to set this DNS resolver as primary one
|
||||||
|
|
|
@ -16,10 +16,13 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.sm;
|
package org.jivesoftware.smack.sm;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
|
import org.jivesoftware.smack.packet.Element;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
|
|
||||||
public abstract class StreamManagementException extends SmackException {
|
public abstract class StreamManagementException extends SmackException {
|
||||||
|
@ -110,5 +113,56 @@ public abstract class StreamManagementException extends SmackException {
|
||||||
return ackedStanzas;
|
return ackedStanzas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class UnacknowledgedQueueFullException extends StreamManagementException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final int overflowElementNum;
|
||||||
|
private final int droppedElements;
|
||||||
|
private final List<Element> elements;
|
||||||
|
private final List<Stanza> unacknowledgesStanzas;
|
||||||
|
|
||||||
|
private UnacknowledgedQueueFullException(String message, int overflowElementNum, int droppedElements, List<Element> elements,
|
||||||
|
List<Stanza> unacknowledgesStanzas) {
|
||||||
|
super(message);
|
||||||
|
this.overflowElementNum = overflowElementNum;
|
||||||
|
this.droppedElements = droppedElements;
|
||||||
|
this.elements = elements;
|
||||||
|
this.unacknowledgesStanzas = unacknowledgesStanzas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOverflowElementNum() {
|
||||||
|
return overflowElementNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDroppedElements() {
|
||||||
|
return droppedElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Element> getElements() {
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Stanza> getUnacknowledgesStanzas() {
|
||||||
|
return unacknowledgesStanzas;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UnacknowledgedQueueFullException newWith(int overflowElementNum, List<Element> elements,
|
||||||
|
BlockingQueue<Stanza> unacknowledgedStanzas) {
|
||||||
|
final int unacknowledgesStanzasQueueSize = unacknowledgedStanzas.size();
|
||||||
|
List<Stanza> localUnacknowledgesStanzas = new ArrayList<>(unacknowledgesStanzasQueueSize);
|
||||||
|
localUnacknowledgesStanzas.addAll(unacknowledgedStanzas);
|
||||||
|
int droppedElements = elements.size() - overflowElementNum - 1;
|
||||||
|
|
||||||
|
String message = "The queue size " + unacknowledgesStanzasQueueSize + " is not able to fit another "
|
||||||
|
+ droppedElements + " potential stanzas type top-level stream-elements.";
|
||||||
|
return new UnacknowledgedQueueFullException(message, overflowElementNum, droppedElements, elements,
|
||||||
|
localUnacknowledgesStanzas);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -83,6 +84,8 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||||
import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
|
import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
|
||||||
|
import org.jivesoftware.smack.SmackException.SmackWrappedException;
|
||||||
|
import org.jivesoftware.smack.SmackFuture;
|
||||||
import org.jivesoftware.smack.StanzaListener;
|
import org.jivesoftware.smack.StanzaListener;
|
||||||
import org.jivesoftware.smack.SynchronizationPoint;
|
import org.jivesoftware.smack.SynchronizationPoint;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
@ -165,15 +168,17 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
|
|
||||||
private SSLSocket secureSocket;
|
private SSLSocket secureSocket;
|
||||||
|
|
||||||
/**
|
private final Semaphore readerWriterSemaphore = new Semaphore(2);
|
||||||
* Protected access level because of unit test purposes
|
|
||||||
*/
|
|
||||||
protected PacketWriter packetWriter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protected access level because of unit test purposes
|
* Protected access level because of unit test purposes
|
||||||
*/
|
*/
|
||||||
protected PacketReader packetReader;
|
protected final PacketWriter packetWriter = new PacketWriter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protected access level because of unit test purposes
|
||||||
|
*/
|
||||||
|
protected final PacketReader packetReader = new PacketReader();
|
||||||
|
|
||||||
private final SynchronizationPoint<Exception> initialOpenStreamSend = new SynchronizationPoint<>(
|
private final SynchronizationPoint<Exception> initialOpenStreamSend = new SynchronizationPoint<>(
|
||||||
this, "initial open stream element send to server");
|
this, "initial open stream element send to server");
|
||||||
|
@ -278,6 +283,15 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
*/
|
*/
|
||||||
private final Collection<StanzaListener> stanzaAcknowledgedListeners = new ConcurrentLinkedQueue<>();
|
private final Collection<StanzaListener> stanzaAcknowledgedListeners = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These listeners are invoked for every stanza that got dropped.
|
||||||
|
* <p>
|
||||||
|
* We use a {@link ConcurrentLinkedQueue} here in order to allow the listeners to remove
|
||||||
|
* themselves after they have been invoked.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private final Collection<StanzaListener> stanzaDroppedListeners = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This listeners are invoked for a acknowledged stanza that has the given stanza ID. They will
|
* This listeners are invoked for a acknowledged stanza that has the given stanza ID. They will
|
||||||
* only be invoked once and automatically removed after that.
|
* only be invoked once and automatically removed after that.
|
||||||
|
@ -387,6 +401,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
SSLSession sslSession = secureSocket != null ? secureSocket.getSession() : null;
|
SSLSession sslSession = secureSocket != null ? secureSocket.getSession() : null;
|
||||||
saslAuthentication.authenticate(username, password, config.getAuthzid(), sslSession);
|
saslAuthentication.authenticate(username, password, config.getAuthzid(), sslSession);
|
||||||
|
|
||||||
|
// Wait for stream features after the authentication.
|
||||||
|
// TODO: The name of this synchronization point "maybeCompressFeaturesReceived" is not perfect. It should be
|
||||||
|
// renamed to "streamFeaturesAfterAuthenticationReceived".
|
||||||
|
maybeCompressFeaturesReceived.checkIfSuccessOrWait();
|
||||||
|
|
||||||
// If compression is enabled then request the server to use stream compression. XEP-170
|
// If compression is enabled then request the server to use stream compression. XEP-170
|
||||||
// recommends to perform stream compression before resource binding.
|
// recommends to perform stream compression before resource binding.
|
||||||
maybeEnableCompression();
|
maybeEnableCompression();
|
||||||
|
@ -437,9 +456,24 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// (Re-)send the stanzas *after* we tried to enable SM
|
// Inform client about failed resumption if possible, resend stanzas otherwise
|
||||||
for (Stanza stanza : previouslyUnackedStanzas) {
|
// Process the stanzas synchronously so a client can re-queue them for transmission
|
||||||
sendStanzaInternal(stanza);
|
// before it is informed about connection success
|
||||||
|
if (!stanzaDroppedListeners.isEmpty()) {
|
||||||
|
for (Stanza stanza : previouslyUnackedStanzas) {
|
||||||
|
for (StanzaListener listener : stanzaDroppedListeners) {
|
||||||
|
try {
|
||||||
|
listener.processStanza(stanza);
|
||||||
|
}
|
||||||
|
catch (InterruptedException | NotConnectedException | NotLoggedInException e) {
|
||||||
|
LOGGER.log(Level.FINER, "StanzaDroppedListener received exception", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Stanza stanza : previouslyUnackedStanzas) {
|
||||||
|
sendStanzaInternal(stanza);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
afterSuccessfulLogin(false);
|
afterSuccessfulLogin(false);
|
||||||
|
@ -468,24 +502,16 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
shutdown(false);
|
shutdown(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Performs an unclean disconnect and shutdown of the connection. Does not send a closing stream stanza.
|
|
||||||
*/
|
|
||||||
public synchronized void instantShutdown() {
|
public synchronized void instantShutdown() {
|
||||||
shutdown(true);
|
shutdown(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutdown(boolean instant) {
|
private void shutdown(boolean instant) {
|
||||||
if (disconnectedButResumeable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First shutdown the writer, this will result in a closing stream element getting send to
|
// First shutdown the writer, this will result in a closing stream element getting send to
|
||||||
// the server
|
// the server
|
||||||
if (packetWriter != null) {
|
LOGGER.finer("PacketWriter shutdown()");
|
||||||
LOGGER.finer("PacketWriter shutdown()");
|
packetWriter.shutdown(instant);
|
||||||
packetWriter.shutdown(instant);
|
|
||||||
}
|
|
||||||
LOGGER.finer("PacketWriter has been shut down");
|
LOGGER.finer("PacketWriter has been shut down");
|
||||||
|
|
||||||
if (!instant) {
|
if (!instant) {
|
||||||
|
@ -500,19 +526,29 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packetReader != null) {
|
LOGGER.finer("PacketReader shutdown()");
|
||||||
LOGGER.finer("PacketReader shutdown()");
|
packetReader.shutdown();
|
||||||
packetReader.shutdown();
|
|
||||||
}
|
|
||||||
LOGGER.finer("PacketReader has been shut down");
|
LOGGER.finer("PacketReader has been shut down");
|
||||||
|
|
||||||
try {
|
final Socket socket = this.socket;
|
||||||
|
if (socket != null && socket.isConnected()) {
|
||||||
|
try {
|
||||||
socket.close();
|
socket.close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.log(Level.WARNING, "shutdown", e);
|
LOGGER.log(Level.WARNING, "shutdown", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setWasAuthenticated();
|
setWasAuthenticated();
|
||||||
|
|
||||||
|
// Wait for reader and writer threads to be terminated.
|
||||||
|
readerWriterSemaphore.acquireUninterruptibly(2);
|
||||||
|
readerWriterSemaphore.release(2);
|
||||||
|
|
||||||
|
if (disconnectedButResumeable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If we are able to resume the stream, then don't set
|
// If we are able to resume the stream, then don't set
|
||||||
// connected/authenticated/usingTLS to false since we like behave like we are still
|
// connected/authenticated/usingTLS to false since we like behave like we are still
|
||||||
// connected (e.g. sendStanza should not throw a NotConnectedException).
|
// connected (e.g. sendStanza should not throw a NotConnectedException).
|
||||||
|
@ -523,6 +559,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
// Reset the stream management session id to null, since if the stream is cleanly closed, i.e. sending a closing
|
// Reset the stream management session id to null, since if the stream is cleanly closed, i.e. sending a closing
|
||||||
// stream tag, there is no longer a stream to resume.
|
// stream tag, there is no longer a stream to resume.
|
||||||
smSessionId = null;
|
smSessionId = null;
|
||||||
|
// Note that we deliberately do not reset authenticatedConnectionInitiallyEstablishedTimestamp here, so that the
|
||||||
|
// information is available in the connectionClosedOnError() listeners.
|
||||||
}
|
}
|
||||||
authenticated = false;
|
authenticated = false;
|
||||||
connected = false;
|
connected = false;
|
||||||
|
@ -530,6 +568,12 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
reader = null;
|
reader = null;
|
||||||
writer = null;
|
writer = null;
|
||||||
|
|
||||||
|
initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initState() {
|
||||||
|
super.initState();
|
||||||
maybeCompressFeaturesReceived.init();
|
maybeCompressFeaturesReceived.init();
|
||||||
compressSyncPoint.init();
|
compressSyncPoint.init();
|
||||||
smResumedSyncPoint.init();
|
smResumedSyncPoint.init();
|
||||||
|
@ -555,7 +599,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connectUsingConfiguration() throws ConnectionException, IOException {
|
private void connectUsingConfiguration() throws ConnectionException, IOException, InterruptedException {
|
||||||
List<HostAddress> failedAddresses = populateHostAddresses();
|
List<HostAddress> failedAddresses = populateHostAddresses();
|
||||||
SocketFactory socketFactory = config.getSocketFactory();
|
SocketFactory socketFactory = config.getSocketFactory();
|
||||||
ProxyInfo proxyInfo = config.getProxyInfo();
|
ProxyInfo proxyInfo = config.getProxyInfo();
|
||||||
|
@ -574,14 +618,16 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
innerloop: while (inetAddresses.hasNext()) {
|
innerloop: while (inetAddresses.hasNext()) {
|
||||||
// Create a *new* Socket before every connection attempt, i.e. connect() call, since Sockets are not
|
// Create a *new* Socket before every connection attempt, i.e. connect() call, since Sockets are not
|
||||||
// re-usable after a failed connection attempt. See also SMACK-724.
|
// re-usable after a failed connection attempt. See also SMACK-724.
|
||||||
socket = socketFactory.createSocket();
|
SmackFuture.SocketFuture socketFuture = new SmackFuture.SocketFuture(socketFactory);
|
||||||
|
|
||||||
final InetAddress inetAddress = inetAddresses.next();
|
final InetAddress inetAddress = inetAddresses.next();
|
||||||
final String inetAddressAndPort = inetAddress + " at port " + port;
|
final InetSocketAddress inetSocketAddress = new InetSocketAddress(inetAddress, port);
|
||||||
LOGGER.finer("Trying to establish TCP connection to " + inetAddressAndPort);
|
LOGGER.finer("Trying to establish TCP connection to " + inetSocketAddress);
|
||||||
|
socketFuture.connectAsync(inetSocketAddress, timeout);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
socket.connect(new InetSocketAddress(inetAddress, port), timeout);
|
socket = socketFuture.getOrThrow();
|
||||||
} catch (Exception e) {
|
} catch (IOException e) {
|
||||||
hostAddress.setException(inetAddress, e);
|
hostAddress.setException(inetAddress, e);
|
||||||
if (inetAddresses.hasNext()) {
|
if (inetAddresses.hasNext()) {
|
||||||
continue innerloop;
|
continue innerloop;
|
||||||
|
@ -589,7 +635,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
break innerloop;
|
break innerloop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LOGGER.finer("Established TCP connection to " + inetAddressAndPort);
|
LOGGER.finer("Established TCP connection to " + inetSocketAddress);
|
||||||
// We found a host to connect to, return here
|
// We found a host to connect to, return here
|
||||||
this.host = host;
|
this.host = host;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
|
@ -605,6 +651,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
proxyInfo.getProxySocketConnection().connect(socket, host, port, timeout);
|
proxyInfo.getProxySocketConnection().connect(socket, host, port, timeout);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
hostAddress.setException(e);
|
hostAddress.setException(e);
|
||||||
|
failedAddresses.add(hostAddress);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
LOGGER.finer("Established TCP connection to " + hostAndPort);
|
LOGGER.finer("Established TCP connection to " + hostAndPort);
|
||||||
|
@ -627,18 +674,23 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
* @throws XMPPException if establishing a connection to the server fails.
|
* @throws XMPPException if establishing a connection to the server fails.
|
||||||
* @throws SmackException if the server fails to respond back or if there is anther error.
|
* @throws SmackException if the server fails to respond back or if there is anther error.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
private void initConnection() throws IOException {
|
private void initConnection() throws IOException, InterruptedException {
|
||||||
boolean isFirstInitialization = packetReader == null || packetWriter == null;
|
|
||||||
compressionHandler = null;
|
compressionHandler = null;
|
||||||
|
|
||||||
// Set the reader and writer instance variables
|
// Set the reader and writer instance variables
|
||||||
initReaderAndWriter();
|
initReaderAndWriter();
|
||||||
|
|
||||||
if (isFirstInitialization) {
|
int availableReaderWriterSemaphorePermits = readerWriterSemaphore.availablePermits();
|
||||||
packetWriter = new PacketWriter();
|
if (availableReaderWriterSemaphorePermits < 2) {
|
||||||
packetReader = new PacketReader();
|
Object[] logObjects = new Object[] {
|
||||||
|
this,
|
||||||
|
availableReaderWriterSemaphorePermits,
|
||||||
|
};
|
||||||
|
LOGGER.log(Level.FINE, "Not every reader/writer threads where terminated on connection re-initializtion of {0}. Available permits {1}", logObjects);
|
||||||
}
|
}
|
||||||
|
readerWriterSemaphore.acquire(2);
|
||||||
// Start the writer thread. This will open an XMPP stream to the server
|
// Start the writer thread. This will open an XMPP stream to the server
|
||||||
packetWriter.init();
|
packetWriter.init();
|
||||||
// Start the reader thread. The startup() method will block until we
|
// Start the reader thread. The startup() method will block until we
|
||||||
|
@ -859,7 +911,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
if (!config.isCompressionEnabled()) {
|
if (!config.isCompressionEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
maybeCompressFeaturesReceived.checkIfSuccessOrWait();
|
|
||||||
Compress.Feature compression = getFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE);
|
Compress.Feature compression = getFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE);
|
||||||
if (compression == null) {
|
if (compression == null) {
|
||||||
// Server does not support compression
|
// Server does not support compression
|
||||||
|
@ -910,18 +962,42 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
*
|
*
|
||||||
* @param e the exception that causes the connection close event.
|
* @param e the exception that causes the connection close event.
|
||||||
*/
|
*/
|
||||||
private synchronized void notifyConnectionError(Exception e) {
|
private void notifyConnectionError(final Exception e) {
|
||||||
// Listeners were already notified of the exception, return right here.
|
ASYNC_BUT_ORDERED.performAsyncButOrdered(this, new Runnable() {
|
||||||
if ((packetReader == null || packetReader.done) &&
|
@Override
|
||||||
(packetWriter == null || packetWriter.done())) return;
|
public void run() {
|
||||||
|
// Listeners were already notified of the exception, return right here.
|
||||||
|
if (packetReader.done || packetWriter.done()) return;
|
||||||
|
|
||||||
// Closes the connection temporary. A reconnection is possible
|
// Report the failure outside the synchronized block, so that a thread waiting within a synchronized
|
||||||
// Note that a connection listener of XMPPTCPConnection will drop the SM state in
|
// function like connect() throws the wrapped exception.
|
||||||
// case the Exception is a StreamErrorException.
|
SmackWrappedException smackWrappedException = new SmackWrappedException(e);
|
||||||
instantShutdown();
|
tlsHandled.reportGenericFailure(smackWrappedException);
|
||||||
|
saslFeatureReceived.reportGenericFailure(smackWrappedException);
|
||||||
|
maybeCompressFeaturesReceived.reportGenericFailure(smackWrappedException);
|
||||||
|
lastFeaturesReceived.reportGenericFailure(smackWrappedException);
|
||||||
|
|
||||||
// Notify connection listeners of the error.
|
synchronized (XMPPTCPConnection.this) {
|
||||||
callConnectionClosedOnErrorListener(e);
|
// Within this synchronized block, either *both* reader and writer threads must be terminated, or
|
||||||
|
// none.
|
||||||
|
assert ((packetReader.done && packetWriter.done())
|
||||||
|
|| (!packetReader.done && !packetWriter.done()));
|
||||||
|
|
||||||
|
// Closes the connection temporary. A reconnection is possible
|
||||||
|
// Note that a connection listener of XMPPTCPConnection will drop the SM state in
|
||||||
|
// case the Exception is a StreamErrorException.
|
||||||
|
instantShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
Async.go(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Notify connection listeners of the error.
|
||||||
|
callConnectionClosedOnErrorListener(e);
|
||||||
|
}
|
||||||
|
}, XMPPTCPConnection.this + " callConnectionClosedOnErrorListener()");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -934,14 +1010,13 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void afterFeaturesReceived() throws NotConnectedException, InterruptedException {
|
protected void afterFeaturesReceived() throws NotConnectedException, InterruptedException, SecurityRequiredByServerException {
|
||||||
StartTls startTlsFeature = getFeature(StartTls.ELEMENT, StartTls.NAMESPACE);
|
StartTls startTlsFeature = getFeature(StartTls.ELEMENT, StartTls.NAMESPACE);
|
||||||
if (startTlsFeature != null) {
|
if (startTlsFeature != null) {
|
||||||
if (startTlsFeature.required() && config.getSecurityMode() == SecurityMode.disabled) {
|
if (startTlsFeature.required() && config.getSecurityMode() == SecurityMode.disabled) {
|
||||||
SmackException smackException = new SecurityRequiredByServerException();
|
SecurityRequiredByServerException smackException = new SecurityRequiredByServerException();
|
||||||
tlsHandled.reportFailure(smackException);
|
tlsHandled.reportFailure(smackException);
|
||||||
notifyConnectionError(smackException);
|
throw smackException;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.getSecurityMode() != ConnectionConfiguration.SecurityMode.disabled) {
|
if (config.getSecurityMode() != ConnectionConfiguration.SecurityMode.disabled) {
|
||||||
|
@ -992,6 +1067,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
|
|
||||||
protected class PacketReader {
|
protected class PacketReader {
|
||||||
|
|
||||||
|
private final String threadName = "Smack Reader (" + getConnectionCounter() + ')';
|
||||||
|
|
||||||
XmlPullParser parser;
|
XmlPullParser parser;
|
||||||
|
|
||||||
private volatile boolean done;
|
private volatile boolean done;
|
||||||
|
@ -1006,9 +1083,15 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
Async.go(new Runnable() {
|
Async.go(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
parsePackets();
|
LOGGER.finer(threadName + " start");
|
||||||
|
try {
|
||||||
|
parsePackets();
|
||||||
|
} finally {
|
||||||
|
LOGGER.finer(threadName + " exit");
|
||||||
|
XMPPTCPConnection.this.readerWriterSemaphore.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, "Smack Reader (" + getConnectionCounter() + ")");
|
}, threadName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1228,7 +1311,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
LOGGER.info(XMPPTCPConnection.this
|
LOGGER.info(XMPPTCPConnection.this
|
||||||
+ " received closing </stream> element."
|
+ " received closing </stream> element."
|
||||||
+ " Server wants to terminate the connection, calling disconnect()");
|
+ " Server wants to terminate the connection, calling disconnect()");
|
||||||
disconnect();
|
ASYNC_BUT_ORDERED.performAsyncButOrdered(XMPPTCPConnection.this, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
disconnect();
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1257,6 +1344,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
protected class PacketWriter {
|
protected class PacketWriter {
|
||||||
public static final int QUEUE_SIZE = XMPPTCPConnection.QUEUE_SIZE;
|
public static final int QUEUE_SIZE = XMPPTCPConnection.QUEUE_SIZE;
|
||||||
|
|
||||||
|
private final String threadName = "Smack Writer (" + getConnectionCounter() + ')';
|
||||||
|
|
||||||
private final ArrayBlockingQueueWithShutdown<Element> queue = new ArrayBlockingQueueWithShutdown<>(
|
private final ArrayBlockingQueueWithShutdown<Element> queue = new ArrayBlockingQueueWithShutdown<>(
|
||||||
QUEUE_SIZE, true);
|
QUEUE_SIZE, true);
|
||||||
|
|
||||||
|
@ -1302,9 +1391,15 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
Async.go(new Runnable() {
|
Async.go(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
writePackets();
|
LOGGER.finer(threadName + " start");
|
||||||
|
try {
|
||||||
|
writePackets();
|
||||||
|
} finally {
|
||||||
|
LOGGER.finer(threadName + " exit");
|
||||||
|
XMPPTCPConnection.this.readerWriterSemaphore.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, "Smack Writer (" + getConnectionCounter() + ")");
|
}, threadName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean done() {
|
private boolean done() {
|
||||||
|
@ -1355,11 +1450,12 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
instantShutdown = instant;
|
instantShutdown = instant;
|
||||||
queue.shutdown();
|
queue.shutdown();
|
||||||
shutdownTimestamp = System.currentTimeMillis();
|
shutdownTimestamp = System.currentTimeMillis();
|
||||||
try {
|
if (shutdownDone.isNotInInitialState()) {
|
||||||
shutdownDone.checkIfSuccessOrWait();
|
try {
|
||||||
}
|
shutdownDone.checkIfSuccessOrWait();
|
||||||
catch (NoResponseException | InterruptedException e) {
|
} catch (NoResponseException | InterruptedException e) {
|
||||||
LOGGER.log(Level.WARNING, "shutdownDone was not marked as successful by the writer thread", e);
|
LOGGER.log(Level.WARNING, "shutdownDone was not marked as successful by the writer thread", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1514,7 +1610,16 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
private void drainWriterQueueToUnacknowledgedStanzas() {
|
private void drainWriterQueueToUnacknowledgedStanzas() {
|
||||||
List<Element> elements = new ArrayList<>(queue.size());
|
List<Element> elements = new ArrayList<>(queue.size());
|
||||||
queue.drainTo(elements);
|
queue.drainTo(elements);
|
||||||
for (Element element : elements) {
|
for (int i = 0; i < elements.size(); i++) {
|
||||||
|
Element element = elements.get(i);
|
||||||
|
// If the unacknowledgedStanza queue is full, then bail out with a warning message. See SMACK-844.
|
||||||
|
if (unacknowledgedStanzas.remainingCapacity() == 0) {
|
||||||
|
StreamManagementException.UnacknowledgedQueueFullException exception = StreamManagementException.UnacknowledgedQueueFullException
|
||||||
|
.newWith(i, elements, unacknowledgedStanzas);
|
||||||
|
LOGGER.log(Level.WARNING,
|
||||||
|
"Some stanzas may be lost as not all could be drained to the unacknowledged stanzas queue", exception);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (element instanceof Stanza) {
|
if (element instanceof Stanza) {
|
||||||
unacknowledgedStanzas.add((Stanza) element);
|
unacknowledgedStanzas.add((Stanza) element);
|
||||||
}
|
}
|
||||||
|
@ -1719,6 +1824,32 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
stanzaAcknowledgedListeners.clear();
|
stanzaAcknowledgedListeners.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a Stanza dropped listener.
|
||||||
|
* <p>
|
||||||
|
* Those listeners will be invoked every time a Stanza has been dropped due to a failed SM resume. They will not get
|
||||||
|
* automatically removed. If at least one StanzaDroppedListener is configured, no attempt will be made to retransmit
|
||||||
|
* the Stanzas.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param listener the listener to add.
|
||||||
|
* @since 4.3.3
|
||||||
|
*/
|
||||||
|
public void addStanzaDroppedListener(StanzaListener listener) {
|
||||||
|
stanzaDroppedListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the given Stanza dropped listener.
|
||||||
|
*
|
||||||
|
* @param listener the listener.
|
||||||
|
* @return true if the listener was removed.
|
||||||
|
* @since 4.3.3
|
||||||
|
*/
|
||||||
|
public boolean removeStanzaDroppedListener(StanzaListener listener) {
|
||||||
|
return stanzaDroppedListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new Stanza ID acknowledged listener for the given ID.
|
* Add a new Stanza ID acknowledged listener for the given ID.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2014 Florian Schmaus
|
* Copyright 2014-2019 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -49,11 +49,9 @@ public class PacketWriterTest {
|
||||||
@Test
|
@Test
|
||||||
public void shouldBlockAndUnblockTest() throws InterruptedException, BrokenBarrierException, NotConnectedException, XmppStringprepException {
|
public void shouldBlockAndUnblockTest() throws InterruptedException, BrokenBarrierException, NotConnectedException, XmppStringprepException {
|
||||||
XMPPTCPConnection connection = new XMPPTCPConnection("user", "pass", "example.org");
|
XMPPTCPConnection connection = new XMPPTCPConnection("user", "pass", "example.org");
|
||||||
final PacketWriter pw = connection.new PacketWriter();
|
final PacketWriter pw = connection.packetWriter;
|
||||||
connection.packetWriter = pw;
|
|
||||||
connection.packetReader = connection.new PacketReader();
|
|
||||||
connection.setWriter(new BlockingStringWriter());
|
connection.setWriter(new BlockingStringWriter());
|
||||||
pw.init();
|
connection.packetWriter.init();
|
||||||
|
|
||||||
for (int i = 0; i < XMPPTCPConnection.PacketWriter.QUEUE_SIZE; i++) {
|
for (int i = 0; i < XMPPTCPConnection.PacketWriter.QUEUE_SIZE; i++) {
|
||||||
pw.sendStreamElement(new Message());
|
pw.sendStreamElement(new Message());
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
allprojects {
|
allprojects {
|
||||||
ext {
|
ext {
|
||||||
shortVersion = '4.3.1'
|
shortVersion = '4.3.4'
|
||||||
isSnapshot = true
|
isSnapshot = true
|
||||||
jxmppVersion = '0.6.3'
|
// When using dynamic versions for those, do *not* use [1.0,
|
||||||
miniDnsVersion = '0.3.2'
|
// 2.0), since this will also pull in 2.0-alpha1. Instead use
|
||||||
|
// [1.0, 1.0.99].
|
||||||
|
// See also:
|
||||||
|
// - https://issues.apache.org/jira/browse/MNG-6232
|
||||||
|
// - https://issues.igniterealtime.org/browse/SMACK-858
|
||||||
|
jxmppVersion = '[0.6, 0.6.999]'
|
||||||
|
miniDnsVersion = '[0.3, 0.3.999]'
|
||||||
smackMinAndroidSdk = 9
|
smackMinAndroidSdk = 9
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue