mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-22 20:12:07 +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">
|
||||
<property name="file" value="config/suppressions.xml"/>
|
||||
</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">
|
||||
<property name="headerFile" value="config/${checkstyleLicenseHeader}.txt"/>
|
||||
<property name="ignoreLines" value="3"/>
|
||||
|
@ -61,6 +66,14 @@
|
|||
<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?"/>
|
||||
</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="TreeWalker">
|
||||
<module name="SuppressionCommentFilter"/>
|
||||
|
@ -89,6 +102,11 @@
|
|||
<property name="message" value="Usage of println"/>
|
||||
<property name="ignoreComments" value="true"/>
|
||||
</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">
|
||||
<property name="format" value="^\t+"/>
|
||||
<property name="message" value="Indent must not use tab characters. Use space instead."/>
|
||||
|
|
|
@ -141,6 +141,74 @@ hr {
|
|||
|
||||
<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> Bug
|
||||
|
|
|
@ -4,5 +4,7 @@ This API is considered beta quality."""
|
|||
|
||||
dependencies {
|
||||
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.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||
import org.jivesoftware.smack.proxy.ProxyInfo;
|
||||
|
@ -34,6 +36,7 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
|||
|
||||
private final boolean https;
|
||||
private final String file;
|
||||
private Map<String, String> httpHeaders;
|
||||
|
||||
private BOSHConfiguration(Builder builder) {
|
||||
super(builder);
|
||||
|
@ -49,6 +52,7 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
|||
} else {
|
||||
file = builder.file;
|
||||
}
|
||||
httpHeaders = builder.httpHeaders;
|
||||
}
|
||||
|
||||
public boolean isProxyEnabled() {
|
||||
|
@ -76,6 +80,10 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
|||
return new URI((https ? "https://" : "http://") + this.host + ":" + this.port + file);
|
||||
}
|
||||
|
||||
public Map<String, String> getHttpHeaders() {
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
@ -83,6 +91,7 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
|||
public static final class Builder extends ConnectionConfiguration.Builder<Builder, BOSHConfiguration> {
|
||||
private boolean https;
|
||||
private String file;
|
||||
private Map<String, String> httpHeaders = new HashMap<>();
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
@ -101,6 +110,11 @@ public final class BOSHConfiguration extends ConnectionConfiguration {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder addHttpHeader(String name, String value) {
|
||||
httpHeaders.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BOSHConfiguration build() {
|
||||
return new BOSHConfiguration(this);
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.io.PipedReader;
|
|||
import java.io.PipedWriter;
|
||||
import java.io.StringReader;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
@ -156,6 +157,9 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
if (config.isProxyEnabled()) {
|
||||
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.addBOSHClientConnListener(new BOSHConnectionListener());
|
||||
|
@ -263,6 +267,11 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
|||
client = null;
|
||||
}
|
||||
|
||||
instantShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void instantShutdown() {
|
||||
setWasAuthenticated();
|
||||
sessionID = null;
|
||||
done = true;
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.LinkedHashMap;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
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
|
||||
* parsed.
|
||||
*/
|
||||
protected final SynchronizationPoint<Exception> lastFeaturesReceived = new SynchronizationPoint<Exception>(
|
||||
protected final SynchronizationPoint<SmackException> lastFeaturesReceived = new SynchronizationPoint<>(
|
||||
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
|
||||
|
@ -314,6 +326,9 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
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
|
||||
* to the server was closed (abruptly or not).
|
||||
|
@ -381,6 +396,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
@Override
|
||||
public abstract boolean isUsingCompression();
|
||||
|
||||
protected void initState() {
|
||||
saslFeatureReceived.init();
|
||||
lastFeaturesReceived.init();
|
||||
tlsHandled.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a connection to the XMPP server. It basically
|
||||
* creates and maintains a connection to the server.
|
||||
|
@ -399,12 +420,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
throwAlreadyConnectedExceptionIfAppropriate();
|
||||
|
||||
// Reset the connection state
|
||||
initState();
|
||||
saslAuthentication.init();
|
||||
saslFeatureReceived.init();
|
||||
lastFeaturesReceived.init();
|
||||
tlsHandled.init();
|
||||
streamId = null;
|
||||
|
||||
try {
|
||||
// Perform the actual connection to the XMPP service
|
||||
connectInternal();
|
||||
|
||||
|
@ -412,9 +432,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
// 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).
|
||||
if (!isSecureConnection() && getConfiguration().getSecurityMode() == SecurityMode.required) {
|
||||
shutdown();
|
||||
throw new SecurityRequiredByClientException();
|
||||
}
|
||||
} catch (SmackException | IOException | XMPPException | InterruptedException e) {
|
||||
instantShutdown();
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Make note of the fact that we're now connected.
|
||||
connected = true;
|
||||
|
@ -550,7 +573,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
// - the servers last features stanza has been parsed
|
||||
// - the timeout occurs
|
||||
LOGGER.finer("Waiting for last features to be received before continuing with resource binding");
|
||||
lastFeaturesReceived.checkIfSuccessOrWait();
|
||||
lastFeaturesReceived.checkIfSuccessOrWaitOrThrow();
|
||||
|
||||
|
||||
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 {
|
||||
if (!resumed) {
|
||||
authenticatedConnectionInitiallyEstablishedTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
// Indicate that we're now authenticated.
|
||||
this.authenticated = true;
|
||||
|
||||
|
@ -763,6 +789,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
*/
|
||||
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
|
||||
public void addConnectionListener(ConnectionListener connectionListener) {
|
||||
if (connectionListener == null) {
|
||||
|
@ -1097,6 +1128,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
if (packet instanceof IQ) {
|
||||
final IQ iq = (IQ) packet;
|
||||
if (iq.isRequestIQ()) {
|
||||
final IQ iqRequest = iq;
|
||||
final String key = XmppStringUtils.generateKey(iq.getChildElementName(), iq.getChildElementNamespace());
|
||||
IQRequestHandler iqRequestHandler;
|
||||
final IQ.Type type = iq.getType();
|
||||
|
@ -1146,7 +1178,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
executorService = ASYNC_BUT_ORDERED.asExecutorFor(this);
|
||||
break;
|
||||
case async:
|
||||
executorService = CACHED_EXECUTOR_SERVICE;
|
||||
executorService = limitedExcutor;
|
||||
break;
|
||||
}
|
||||
final IQRequestHandler finalIqRequestHandler = iqRequestHandler;
|
||||
|
@ -1162,6 +1194,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
// e.g. to avoid presence leaks.
|
||||
return;
|
||||
}
|
||||
|
||||
assert (response.getType() == IQ.Type.result || response.getType() == IQ.Type.error);
|
||||
|
||||
response.setTo(iqRequest.getFrom());
|
||||
response.setStanzaId(iqRequest.getStanzaId());
|
||||
try {
|
||||
sendStanza(response);
|
||||
}
|
||||
|
@ -1191,7 +1228,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
}
|
||||
|
||||
for (final StanzaListener listener : listenersToNotify) {
|
||||
asyncGo(new Runnable() {
|
||||
asyncGoLimited(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
@ -1710,6 +1747,18 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
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
|
||||
* stanza.
|
||||
|
@ -1736,6 +1785,75 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
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) {
|
||||
CACHED_EXECUTOR_SERVICE.execute(runnable);
|
||||
}
|
||||
|
|
|
@ -55,6 +55,16 @@ public class AsyncButOrdered<K> {
|
|||
|
||||
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.
|
||||
*
|
||||
|
@ -86,7 +96,11 @@ public class AsyncButOrdered<K> {
|
|||
if (newHandler) {
|
||||
Handler handler = new Handler(keyQueue, key);
|
||||
threadActiveMap.put(key, true);
|
||||
if (executor == null) {
|
||||
AbstractXMPPConnection.asyncGo(handler);
|
||||
} else {
|
||||
executor.execute(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.jivesoftware.smack;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -45,6 +46,7 @@ import org.jxmpp.jid.impl.JidCreate;
|
|||
import org.jxmpp.jid.parts.Resourcepart;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
import org.minidns.dnsname.DnsName;
|
||||
import org.minidns.util.InetAddressUtil;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -642,6 +652,33 @@ public abstract class ConnectionConfiguration {
|
|||
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) {
|
||||
if (port < 0 || port > 65535) {
|
||||
throw new IllegalArgumentException(
|
||||
|
|
|
@ -365,4 +365,19 @@ public final class SmackConfiguration {
|
|||
public static void setUnknownIqRequestReplyMode(UnknownIqRequestReplyMode unknownIqRequestReplyMode) {
|
||||
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());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.4.
|
||||
public static NoResponseException newWith(XMPPConnection connection,
|
||||
StanzaCollector collector) {
|
||||
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) {
|
||||
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: ");
|
||||
if (filter != null) {
|
||||
sb.append(filter.toString());
|
||||
|
@ -112,7 +123,10 @@ public class SmackException extends Exception {
|
|||
}
|
||||
|
||||
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);
|
||||
sb.append("No response received within reply timeout. Timeout was "
|
||||
+ replyTimeout + "ms (~"
|
||||
|
@ -334,4 +348,16 @@ public class SmackException extends Exception {
|
|||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
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.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> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(SmackFuture.class.getName());
|
||||
|
||||
private boolean cancelled;
|
||||
|
||||
protected V result;
|
||||
|
@ -94,7 +103,7 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
|||
@Override
|
||||
public final synchronized V get() throws InterruptedException, ExecutionException {
|
||||
while (result == null && exception == null && !cancelled) {
|
||||
wait();
|
||||
futureWait();
|
||||
}
|
||||
|
||||
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 {
|
||||
while (result == null && exception == null && !cancelled) {
|
||||
wait();
|
||||
futureWait();
|
||||
}
|
||||
|
||||
if (exception != null) {
|
||||
|
@ -124,7 +133,7 @@ public abstract class SmackFuture<V, E extends Exception> implements Future<V>,
|
|||
while (result != null && exception != null) {
|
||||
final long waitTimeRemaining = deadline - System.currentTimeMillis();
|
||||
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 final synchronized void setResult(V 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>
|
||||
implements StanzaListener, ExceptionCallback<E> {
|
||||
|
||||
|
|
|
@ -262,7 +262,7 @@ public class StanzaCollector {
|
|||
if (!connection.isConnected()) {
|
||||
throw new NotConnectedException(connection, packetFilter);
|
||||
}
|
||||
throw NoResponseException.newWith(connection, this);
|
||||
throw NoResponseException.newWith(timeout, this);
|
||||
}
|
||||
|
||||
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");
|
||||
* 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.NotConnectedException;
|
||||
import org.jivesoftware.smack.SmackException.SmackWrappedException;
|
||||
import org.jivesoftware.smack.packet.Nonza;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
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.
|
||||
private State state;
|
||||
private E failureException;
|
||||
private SmackWrappedException smackWrappedExcpetion;
|
||||
|
||||
/**
|
||||
* Construct a new synchronization point for the given connection.
|
||||
|
@ -59,6 +61,7 @@ public class SynchronizationPoint<E extends Exception> {
|
|||
connectionLock.lock();
|
||||
state = State.Initial;
|
||||
failureException = null;
|
||||
smackWrappedExcpetion = null;
|
||||
connectionLock.unlock();
|
||||
}
|
||||
|
||||
|
@ -71,7 +74,7 @@ public class SynchronizationPoint<E extends Exception> {
|
|||
* @throws InterruptedException if the connection is interrupted.
|
||||
* @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 {
|
||||
assert (state == State.Initial);
|
||||
connectionLock.lock();
|
||||
|
@ -103,15 +106,14 @@ public class SynchronizationPoint<E extends Exception> {
|
|||
* @throws NoResponseException if no response was received.
|
||||
* @throws NotConnectedException if the connection is not connected.
|
||||
* @throws InterruptedException if the connection is interrupted.
|
||||
* @throws SmackWrappedException in case of a wrapped exception;
|
||||
*/
|
||||
public void sendAndWaitForResponseOrThrow(Nonza request) throws E, NoResponseException,
|
||||
NotConnectedException, InterruptedException {
|
||||
NotConnectedException, InterruptedException, SmackWrappedException {
|
||||
sendAndWaitForResponse(request);
|
||||
switch (state) {
|
||||
case Failure:
|
||||
if (failureException != null) {
|
||||
throw failureException;
|
||||
}
|
||||
throwException();
|
||||
break;
|
||||
default:
|
||||
// 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 E if there was a failure
|
||||
* @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();
|
||||
if (state == State.Failure) {
|
||||
throw failureException;
|
||||
throwException();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,7 +140,7 @@ public class SynchronizationPoint<E extends Exception> {
|
|||
* @throws InterruptedException
|
||||
* @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();
|
||||
try {
|
||||
switch (state) {
|
||||
|
@ -145,7 +148,7 @@ public class SynchronizationPoint<E extends Exception> {
|
|||
case Success:
|
||||
return null;
|
||||
case Failure:
|
||||
return failureException;
|
||||
return getException();
|
||||
default:
|
||||
// Do nothing
|
||||
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.
|
||||
*
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
* <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.
|
||||
* @throws NoResponseException
|
||||
*/
|
||||
private E checkForResponse() throws NoResponseException {
|
||||
private Exception checkForResponse() throws NoResponseException {
|
||||
switch (state) {
|
||||
case Initial:
|
||||
case NoResponse:
|
||||
|
@ -273,7 +318,7 @@ public class SynchronizationPoint<E extends Exception> {
|
|||
case Success:
|
||||
return null;
|
||||
case Failure:
|
||||
return failureException;
|
||||
return getException();
|
||||
default:
|
||||
throw new AssertionError("Unknown state " + state);
|
||||
}
|
||||
|
|
|
@ -198,6 +198,12 @@ public class StanzaError extends AbstractError implements ExtensionElement {
|
|||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("XMPPError: ");
|
||||
sb.append(condition.toString()).append(" - ").append(type.toString());
|
||||
|
||||
String descriptiveText = getDescriptiveText();
|
||||
if (descriptiveText != null) {
|
||||
sb.append(" [").append(descriptiveText).append(']');
|
||||
}
|
||||
|
||||
if (errorGenerator != null) {
|
||||
sb.append(". Generated by ").append(errorGenerator);
|
||||
}
|
||||
|
@ -385,11 +391,6 @@ public class StanzaError extends AbstractError implements ExtensionElement {
|
|||
}
|
||||
|
||||
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('-', '_');
|
||||
Condition condition = null;
|
||||
try {
|
||||
|
|
|
@ -186,6 +186,11 @@ public class StreamError extends AbstractError implements Nonza {
|
|||
}
|
||||
|
||||
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('-', '_');
|
||||
Condition condition = null;
|
||||
try {
|
||||
|
|
|
@ -123,7 +123,9 @@ public class IntrospectionProvider{
|
|||
case "java.lang.String":
|
||||
return value;
|
||||
case "boolean":
|
||||
// CHECKSTYLE:OFF
|
||||
return Boolean.valueOf(value);
|
||||
// CHECKSTYLE:ON
|
||||
case "int":
|
||||
return Integer.valueOf(value);
|
||||
case "long":
|
||||
|
|
|
@ -58,7 +58,7 @@ class HTTPProxySocketConnection implements ProxySocketConnection {
|
|||
proxyLine = "\r\nProxy-Authorization: Basic " + Base64.encode(username + ":" + password);
|
||||
}
|
||||
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();
|
||||
StringBuilder got = new StringBuilder(100);
|
||||
|
@ -115,7 +115,8 @@ class HTTPProxySocketConnection implements ProxySocketConnection {
|
|||
int code = Integer.parseInt(m.group(1));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -141,7 +162,7 @@ public class ParserUtils {
|
|||
if (valueString == null)
|
||||
return null;
|
||||
valueString = valueString.toLowerCase(Locale.US);
|
||||
return valueString.equals("true") || valueString.equals("0");
|
||||
return parseXmlBoolean(valueString);
|
||||
}
|
||||
|
||||
public static boolean getBooleanAttribute(XmlPullParser parser, String name,
|
||||
|
|
|
@ -35,7 +35,11 @@ public class XmlUtil {
|
|||
private static final TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
|
||||
static {
|
||||
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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void instantShutdown() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecureConnection() {
|
||||
return false;
|
||||
|
@ -226,4 +231,5 @@ public class DummyConnection extends AbstractXMPPConnection {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -104,4 +104,22 @@ public class StreamErrorTest {
|
|||
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;
|
||||
|
||||
import org.jivesoftware.smack.util.ParserUtils;
|
||||
|
||||
import org.jivesoftware.smackx.hoxt.packet.HttpMethod;
|
||||
import org.jivesoftware.smackx.hoxt.packet.HttpOverXmppReq;
|
||||
|
||||
|
@ -47,13 +49,13 @@ public class HttpOverXmppReqProvider extends AbstractHttpOverXmppProvider<HttpOv
|
|||
String jingleStr = parser.getAttributeValue("", AbstractHttpOverXmppProvider.ELEMENT_JINGLE);
|
||||
|
||||
if (sipubStr != null) {
|
||||
builder.setSipub(Boolean.valueOf(sipubStr));
|
||||
builder.setSipub(ParserUtils.parseXmlBoolean(sipubStr));
|
||||
}
|
||||
if (ibbStr != null) {
|
||||
builder.setIbb(Boolean.valueOf(ibbStr));
|
||||
builder.setIbb(ParserUtils.parseXmlBoolean(ibbStr));
|
||||
}
|
||||
if (jingleStr != null) {
|
||||
builder.setJingle(Boolean.valueOf(jingleStr));
|
||||
builder.setJingle(ParserUtils.parseXmlBoolean(jingleStr));
|
||||
}
|
||||
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.iot.control.element;
|
||||
|
||||
import org.jivesoftware.smack.util.ParserUtils;
|
||||
|
||||
public class SetBoolData extends SetData {
|
||||
|
||||
public SetBoolData(String name, boolean value) {
|
||||
|
@ -31,7 +33,7 @@ public class SetBoolData extends SetData {
|
|||
|
||||
public Boolean getBooleanValue() {
|
||||
if (booleanCache != null) {
|
||||
booleanCache = Boolean.valueOf(getValue());
|
||||
booleanCache = ParserUtils.parseXmlBoolean(getValue());
|
||||
}
|
||||
return booleanCache;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ public final class JingleFileTransferManager extends Manager {
|
|||
super(connection);
|
||||
}
|
||||
|
||||
public static JingleFileTransferManager getInstanceFor(XMPPConnection connection) {
|
||||
public static synchronized JingleFileTransferManager getInstanceFor(XMPPConnection connection) {
|
||||
JingleFileTransferManager manager = INSTANCES.get(connection);
|
||||
if (manager == null) {
|
||||
manager = new JingleFileTransferManager(connection);
|
||||
|
|
|
@ -114,8 +114,8 @@ import org.jxmpp.jid.Jid;
|
|||
* <pre>
|
||||
* {@code
|
||||
* MamQueryArgs mamQueryArgs = MamQueryArgs.builder()
|
||||
* .withJid(jid)
|
||||
* .setResultPageSize(10)
|
||||
* .limitResultsToJid(jid)
|
||||
* .setResultPageSizeTo(10)
|
||||
* .queryLastPage()
|
||||
* .build();
|
||||
* 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.
|
||||
* @return the instance of MamManager.
|
||||
*/
|
||||
// CHECKSTYLE:OFF:RegexpSingleline
|
||||
public static MamManager getInstanceFor(XMPPConnection connection) {
|
||||
// CHECKSTYLE:ON:RegexpSingleline
|
||||
return getInstanceFor(connection, (Jid) null);
|
||||
}
|
||||
|
||||
|
@ -995,7 +997,7 @@ public final class MamManager extends Manager {
|
|||
public List<Message> pagePrevious(int count) throws NoResponseException, XMPPErrorException,
|
||||
NotConnectedException, NotLoggedInException, InterruptedException {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ public final class ReferenceManager extends Manager {
|
|||
* @param connection xmpp connection
|
||||
* @return reference manager instance
|
||||
*/
|
||||
public static ReferenceManager getInstanceFor(XMPPConnection connection) {
|
||||
public static synchronized ReferenceManager getInstanceFor(XMPPConnection connection) {
|
||||
ReferenceManager manager = INSTANCES.get(connection);
|
||||
if (manager == null) {
|
||||
manager = new ReferenceManager(connection);
|
||||
|
|
|
@ -27,7 +27,6 @@ import org.jivesoftware.smack.XMPPConnectionRegistry;
|
|||
import org.jivesoftware.smack.filter.AndFilter;
|
||||
import org.jivesoftware.smack.filter.MessageTypeFilter;
|
||||
import org.jivesoftware.smack.filter.NotFilter;
|
||||
import org.jivesoftware.smack.filter.StanzaExtensionFilter;
|
||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||
import org.jivesoftware.smack.filter.ToTypeFilter;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
|
@ -46,13 +45,12 @@ public final class StableUniqueStanzaIdManager extends Manager {
|
|||
MessageTypeFilter.NORMAL_OR_CHAT_OR_HEADLINE,
|
||||
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.
|
||||
private final StanzaListener stanzaListener = new StanzaListener() {
|
||||
private static final StanzaListener ADD_ORIGIN_ID_INTERCEPTOR = new StanzaListener() {
|
||||
@Override
|
||||
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
|
||||
* @return manager instance for the connection
|
||||
*/
|
||||
public static StableUniqueStanzaIdManager getInstanceFor(XMPPConnection connection) {
|
||||
public static synchronized StableUniqueStanzaIdManager getInstanceFor(XMPPConnection connection) {
|
||||
StableUniqueStanzaIdManager manager = INSTANCES.get(connection);
|
||||
if (manager == null) {
|
||||
manager = new StableUniqueStanzaIdManager(connection);
|
||||
|
@ -95,7 +93,7 @@ public final class StableUniqueStanzaIdManager extends Manager {
|
|||
public synchronized void enable() {
|
||||
ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE);
|
||||
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() {
|
||||
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 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
|
||||
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 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
|
||||
public StanzaIdElement parse(XmlPullParser parser, int initialDepth) throws Exception {
|
||||
|
|
|
@ -61,7 +61,7 @@ public final class SpoilerManager extends Manager {
|
|||
* @param connection xmpp connection
|
||||
* @return SpoilerManager
|
||||
*/
|
||||
public static SpoilerManager getInstanceFor(XMPPConnection connection) {
|
||||
public static synchronized SpoilerManager getInstanceFor(XMPPConnection connection) {
|
||||
SpoilerManager manager = INSTANCES.get(connection);
|
||||
if (manager == null) {
|
||||
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.test.util.SmackTestSuite;
|
||||
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.StanzaIdElement;
|
||||
import org.jivesoftware.smackx.sid.provider.OriginIdProvider;
|
||||
|
@ -42,7 +43,7 @@ public class StableUniqueStanzaIdTest extends SmackTestSuite {
|
|||
assertEquals("alice@wonderland.lit", element.getBy());
|
||||
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.getBy(), parsed.getBy());
|
||||
}
|
||||
|
@ -54,7 +55,7 @@ public class StableUniqueStanzaIdTest extends SmackTestSuite {
|
|||
assertEquals("de305d54-75b4-431b-adb2-eb6b9e546013", element.getId());
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -81,4 +82,17 @@ public class StableUniqueStanzaIdTest extends SmackTestSuite {
|
|||
assertTrue(StanzaIdElement.hasStanzaId(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 {
|
||||
String name = parser.getAttributeValue("", "name");
|
||||
String autojoin = parser.getAttributeValue("", "autojoin");
|
||||
boolean autojoin = ParserUtils.getBooleanAttribute(parser, "autojoin", false);
|
||||
EntityBareJid jid = ParserUtils.getBareJidAttribute(parser);
|
||||
|
||||
BookmarkedConference conf = new BookmarkedConference(jid);
|
||||
conf.setName(name);
|
||||
conf.setAutoJoin(Boolean.valueOf(autojoin));
|
||||
conf.setAutoJoin(autojoin);
|
||||
|
||||
// Check for nickname
|
||||
boolean done = false;
|
||||
|
|
|
@ -211,6 +211,7 @@ public final class Socks5Proxy {
|
|||
if (this.serverSocket != null) {
|
||||
this.serverThread = new Thread(this.serverProcess);
|
||||
this.serverThread.setName("Smack Local SOCKS5 Proxy");
|
||||
this.serverThread.setDaemon(true);
|
||||
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");
|
||||
* 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
|
||||
* 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
|
||||
* disco request.
|
||||
|
@ -271,8 +271,8 @@ public final class ServiceDiscoveryManager extends Manager {
|
|||
*/
|
||||
public Set<DiscoverInfo.Identity> getIdentities() {
|
||||
Set<Identity> res = new HashSet<>(identities);
|
||||
// Add the default identity that must exist
|
||||
res.add(defaultIdentity);
|
||||
// Add the main identity that must exist
|
||||
res.add(identity);
|
||||
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
|
||||
* 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>
|
||||
* 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
|
||||
* '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.
|
||||
*/
|
||||
|
@ -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
|
||||
* '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.
|
||||
*/
|
||||
|
|
|
@ -48,7 +48,7 @@ public final class JingleTransportMethodManager extends Manager {
|
|||
super(connection);
|
||||
}
|
||||
|
||||
public static JingleTransportMethodManager getInstanceFor(XMPPConnection connection) {
|
||||
public static synchronized JingleTransportMethodManager getInstanceFor(XMPPConnection connection) {
|
||||
JingleTransportMethodManager manager = INSTANCES.get(connection);
|
||||
if (manager == null) {
|
||||
manager = new JingleTransportMethodManager(connection);
|
||||
|
|
|
@ -38,7 +38,7 @@ public final class JingleIBBTransportManager extends JingleTransportManager<Jing
|
|||
JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleIBBTransportProvider());
|
||||
}
|
||||
|
||||
public static JingleIBBTransportManager getInstanceFor(XMPPConnection connection) {
|
||||
public static synchronized JingleIBBTransportManager getInstanceFor(XMPPConnection connection) {
|
||||
JingleIBBTransportManager manager = INSTANCES.get(connection);
|
||||
if (manager == null) {
|
||||
manager = new JingleIBBTransportManager(connection);
|
||||
|
|
|
@ -63,7 +63,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager<Jing
|
|||
JingleContentProviderManager.addJingleContentTransportProvider(getNamespace(), new JingleS5BTransportProvider());
|
||||
}
|
||||
|
||||
public static JingleS5BTransportManager getInstanceFor(XMPPConnection connection) {
|
||||
public static synchronized JingleS5BTransportManager getInstanceFor(XMPPConnection connection) {
|
||||
JingleS5BTransportManager manager = INSTANCES.get(connection);
|
||||
if (manager == null) {
|
||||
manager = new JingleS5BTransportManager(connection);
|
||||
|
|
|
@ -92,7 +92,9 @@ public class JivePropertiesExtensionProvider extends ExtensionElementProvider<Ji
|
|||
value = Double.valueOf(valueText);
|
||||
}
|
||||
else if ("boolean".equals(type)) {
|
||||
// CHECKSTYLE:OFF
|
||||
value = Boolean.valueOf(valueText);
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
else if ("string".equals(type)) {
|
||||
value = valueText;
|
||||
|
|
|
@ -205,7 +205,6 @@ public class MultiUserChat {
|
|||
final Presence presence = (Presence) packet;
|
||||
final EntityFullJid from = presence.getFrom().asEntityFullJidIfPossible();
|
||||
if (from == null) {
|
||||
LOGGER.warning("Presence not from a full JID: " + presence.getFrom());
|
||||
return;
|
||||
}
|
||||
final EntityFullJid myRoomJID = myRoomJid;
|
||||
|
@ -341,8 +340,9 @@ public class MultiUserChat {
|
|||
|
||||
// Setup the messageListeners and presenceListeners *before* the join presence is send.
|
||||
connection.addSyncStanzaListener(messageListener, fromRoomGroupchatFilter);
|
||||
connection.addSyncStanzaListener(presenceListener, new AndFilter(fromRoomFilter,
|
||||
StanzaTypeFilter.PRESENCE));
|
||||
StanzaFilter presenceFromRoomFilter = new AndFilter(fromRoomFilter,
|
||||
StanzaTypeFilter.PRESENCE);
|
||||
connection.addSyncStanzaListener(presenceListener, presenceFromRoomFilter);
|
||||
// @formatter:off
|
||||
connection.addSyncStanzaListener(subjectListener,
|
||||
new AndFilter(fromRoomFilter,
|
||||
|
@ -371,15 +371,27 @@ public class MultiUserChat {
|
|||
)
|
||||
);
|
||||
// @formatter:on
|
||||
StanzaCollector presenceStanzaCollector = null;
|
||||
Presence presence;
|
||||
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) {
|
||||
// Ensure that all callbacks are removed if there is an exception
|
||||
removeConnectionCallbacks();
|
||||
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
|
||||
// performed roomnick rewriting
|
||||
|
@ -735,7 +747,12 @@ public class MultiUserChat {
|
|||
// If we've already joined the room, leave it before joining under a new
|
||||
// nickname.
|
||||
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);
|
||||
}
|
||||
|
@ -776,6 +793,50 @@ public class MultiUserChat {
|
|||
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.
|
||||
* <p>
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.jivesoftware.smackx.muc;
|
|||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -392,19 +393,46 @@ public final class MultiUserChatManager extends Manager {
|
|||
* @throws NotConnectedException
|
||||
* @throws InterruptedException
|
||||
* @throws NotAMucServiceException
|
||||
* @deprecated use {@link #getRoomsHostedBy(DomainBareJid)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Remove in Smack 4.4.
|
||||
public List<HostedRoom> getHostedRooms(DomainBareJid serviceName) throws NoResponseException, XMPPErrorException,
|
||||
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)) {
|
||||
throw new NotAMucServiceException(serviceName);
|
||||
}
|
||||
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection());
|
||||
DiscoverItems discoverItems = discoManager.discoverItems(serviceName);
|
||||
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) {
|
||||
answer.add(new HostedRoom(item));
|
||||
HostedRoom hostedRoom = new HostedRoom(item);
|
||||
HostedRoom previousRoom = answer.put(hostedRoom.getJid(), hostedRoom);
|
||||
assert previousRoom == null;
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
|
|
|
@ -207,7 +207,8 @@ public class RoomInfo {
|
|||
|
||||
FormField subjectmodField = form.getField("muc#roominfo_subjectmod");
|
||||
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");
|
||||
|
|
|
@ -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");
|
||||
* 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 {
|
||||
|
||||
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,
|
||||
new StanzaExtensionFilter(DeliveryReceipt.ELEMENT, DeliveryReceipt.NAMESPACE));
|
||||
|
||||
|
@ -175,7 +183,7 @@ public final class DeliveryReceiptManager extends Manager {
|
|||
}
|
||||
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() {
|
||||
CharSequence firstValue;
|
||||
|
||||
synchronized (values) {
|
||||
firstValue = values.get(0);
|
||||
}
|
||||
if (firstValue == null) {
|
||||
if (values.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
firstValue = values.get(0);
|
||||
}
|
||||
|
||||
return firstValue.toString();
|
||||
}
|
||||
|
||||
|
|
|
@ -951,8 +951,7 @@ public final class Roster extends Manager {
|
|||
// This is used in case no available presence is found
|
||||
Presence unavailable = null;
|
||||
|
||||
for (Resourcepart resource : userPresences.keySet()) {
|
||||
Presence p = userPresences.get(resource);
|
||||
for (Presence p : userPresences.values()) {
|
||||
if (!p.isAvailable()) {
|
||||
unavailable = p;
|
||||
continue;
|
||||
|
@ -1472,7 +1471,29 @@ public final class Roster extends Manager {
|
|||
final Presence presence = (Presence) packet;
|
||||
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() {
|
||||
@Override
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.muc;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.XMPPException.XMPPErrorException;
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.packet.Presence;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
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.SmackIntegrationTest;
|
||||
|
@ -36,6 +43,7 @@ import org.jxmpp.jid.EntityBareJid;
|
|||
import org.jxmpp.jid.impl.JidCreate;
|
||||
import org.jxmpp.jid.parts.Localpart;
|
||||
import org.jxmpp.jid.parts.Resourcepart;
|
||||
import org.jxmpp.stringprep.XmppStringprepException;
|
||||
|
||||
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
|
||||
public void mucTest() throws Exception {
|
||||
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
|
||||
* @return a Jingle session
|
||||
*/
|
||||
public static JingleSession getInstanceFor(XMPPConnection con) {
|
||||
public static synchronized JingleSession getInstanceFor(XMPPConnection con) {
|
||||
if (con == null) {
|
||||
throw new IllegalArgumentException("XMPPConnection cannot be null");
|
||||
}
|
||||
|
|
|
@ -104,7 +104,9 @@ public class WorkgroupProperties extends IQ {
|
|||
while (!done) {
|
||||
int eventType = parser.next();
|
||||
if ((eventType == XmlPullParser.START_TAG) && ("authRequired".equals(parser.getName()))) {
|
||||
// CHECKSTYLE:OFF
|
||||
props.setAuthRequired(Boolean.valueOf(parser.nextText()).booleanValue());
|
||||
// CHECKSTYLE:ON
|
||||
}
|
||||
else if ((eventType == XmlPullParser.START_TAG) && ("email".equals(parser.getName()))) {
|
||||
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");
|
||||
* 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.
|
||||
* 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
|
||||
*
|
||||
|
@ -55,8 +56,8 @@ public class JavaxResolver extends DNSResolver implements SmackInitializer {
|
|||
Hashtable<String, String> env = new Hashtable<>();
|
||||
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
|
||||
dirContext = new InitialDirContext(env);
|
||||
} catch (Exception e) {
|
||||
// Ignore.
|
||||
} catch (NamingException e) {
|
||||
LOGGER.log(Level.SEVERE, "Could not construct InitialDirContext", e);
|
||||
}
|
||||
|
||||
// Try to set this DNS resolver as primary one
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
*/
|
||||
package org.jivesoftware.smack.sm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
import org.jivesoftware.smack.SmackException;
|
||||
import org.jivesoftware.smack.packet.Element;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
|
||||
public abstract class StreamManagementException extends SmackException {
|
||||
|
@ -110,5 +113,56 @@ public abstract class StreamManagementException extends SmackException {
|
|||
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.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
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.NotLoggedInException;
|
||||
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.SynchronizationPoint;
|
||||
import org.jivesoftware.smack.XMPPConnection;
|
||||
|
@ -165,15 +168,17 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
|
||||
private SSLSocket secureSocket;
|
||||
|
||||
/**
|
||||
* Protected access level because of unit test purposes
|
||||
*/
|
||||
protected PacketWriter packetWriter;
|
||||
private final Semaphore readerWriterSemaphore = new Semaphore(2);
|
||||
|
||||
/**
|
||||
* 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<>(
|
||||
this, "initial open stream element send to server");
|
||||
|
@ -278,6 +283,15 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
*/
|
||||
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
|
||||
* 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;
|
||||
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
|
||||
// recommends to perform stream compression before resource binding.
|
||||
maybeEnableCompression();
|
||||
|
@ -437,10 +456,25 @@ 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
|
||||
// Process the stanzas synchronously so a client can re-queue them for transmission
|
||||
// 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);
|
||||
}
|
||||
|
@ -468,24 +502,16 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
shutdown(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an unclean disconnect and shutdown of the connection. Does not send a closing stream stanza.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void instantShutdown() {
|
||||
shutdown(true);
|
||||
}
|
||||
|
||||
private void shutdown(boolean instant) {
|
||||
if (disconnectedButResumeable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First shutdown the writer, this will result in a closing stream element getting send to
|
||||
// the server
|
||||
if (packetWriter != null) {
|
||||
LOGGER.finer("PacketWriter shutdown()");
|
||||
packetWriter.shutdown(instant);
|
||||
}
|
||||
LOGGER.finer("PacketWriter has been shut down");
|
||||
|
||||
if (!instant) {
|
||||
|
@ -500,19 +526,29 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
}
|
||||
}
|
||||
|
||||
if (packetReader != null) {
|
||||
LOGGER.finer("PacketReader shutdown()");
|
||||
packetReader.shutdown();
|
||||
}
|
||||
LOGGER.finer("PacketReader has been shut down");
|
||||
|
||||
final Socket socket = this.socket;
|
||||
if (socket != null && socket.isConnected()) {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.WARNING, "shutdown", e);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// connected/authenticated/usingTLS to false since we like behave like we are still
|
||||
// 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
|
||||
// stream tag, there is no longer a stream to resume.
|
||||
smSessionId = null;
|
||||
// Note that we deliberately do not reset authenticatedConnectionInitiallyEstablishedTimestamp here, so that the
|
||||
// information is available in the connectionClosedOnError() listeners.
|
||||
}
|
||||
authenticated = false;
|
||||
connected = false;
|
||||
|
@ -530,6 +568,12 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
reader = null;
|
||||
writer = null;
|
||||
|
||||
initState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initState() {
|
||||
super.initState();
|
||||
maybeCompressFeaturesReceived.init();
|
||||
compressSyncPoint.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();
|
||||
SocketFactory socketFactory = config.getSocketFactory();
|
||||
ProxyInfo proxyInfo = config.getProxyInfo();
|
||||
|
@ -574,14 +618,16 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
innerloop: while (inetAddresses.hasNext()) {
|
||||
// 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.
|
||||
socket = socketFactory.createSocket();
|
||||
SmackFuture.SocketFuture socketFuture = new SmackFuture.SocketFuture(socketFactory);
|
||||
|
||||
final InetAddress inetAddress = inetAddresses.next();
|
||||
final String inetAddressAndPort = inetAddress + " at port " + port;
|
||||
LOGGER.finer("Trying to establish TCP connection to " + inetAddressAndPort);
|
||||
final InetSocketAddress inetSocketAddress = new InetSocketAddress(inetAddress, port);
|
||||
LOGGER.finer("Trying to establish TCP connection to " + inetSocketAddress);
|
||||
socketFuture.connectAsync(inetSocketAddress, timeout);
|
||||
|
||||
try {
|
||||
socket.connect(new InetSocketAddress(inetAddress, port), timeout);
|
||||
} catch (Exception e) {
|
||||
socket = socketFuture.getOrThrow();
|
||||
} catch (IOException e) {
|
||||
hostAddress.setException(inetAddress, e);
|
||||
if (inetAddresses.hasNext()) {
|
||||
continue innerloop;
|
||||
|
@ -589,7 +635,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
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
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
|
@ -605,6 +651,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
proxyInfo.getProxySocketConnection().connect(socket, host, port, timeout);
|
||||
} catch (IOException e) {
|
||||
hostAddress.setException(e);
|
||||
failedAddresses.add(hostAddress);
|
||||
continue;
|
||||
}
|
||||
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 SmackException if the server fails to respond back or if there is anther error.
|
||||
* @throws IOException
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
private void initConnection() throws IOException {
|
||||
boolean isFirstInitialization = packetReader == null || packetWriter == null;
|
||||
private void initConnection() throws IOException, InterruptedException {
|
||||
compressionHandler = null;
|
||||
|
||||
// Set the reader and writer instance variables
|
||||
initReaderAndWriter();
|
||||
|
||||
if (isFirstInitialization) {
|
||||
packetWriter = new PacketWriter();
|
||||
packetReader = new PacketReader();
|
||||
int availableReaderWriterSemaphorePermits = readerWriterSemaphore.availablePermits();
|
||||
if (availableReaderWriterSemaphorePermits < 2) {
|
||||
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
|
||||
packetWriter.init();
|
||||
// Start the reader thread. The startup() method will block until we
|
||||
|
@ -859,7 +911,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
if (!config.isCompressionEnabled()) {
|
||||
return;
|
||||
}
|
||||
maybeCompressFeaturesReceived.checkIfSuccessOrWait();
|
||||
|
||||
Compress.Feature compression = getFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE);
|
||||
if (compression == null) {
|
||||
// Server does not support compression
|
||||
|
@ -910,19 +962,43 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
*
|
||||
* @param e the exception that causes the connection close event.
|
||||
*/
|
||||
private synchronized void notifyConnectionError(Exception e) {
|
||||
private void notifyConnectionError(final Exception e) {
|
||||
ASYNC_BUT_ORDERED.performAsyncButOrdered(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Listeners were already notified of the exception, return right here.
|
||||
if ((packetReader == null || packetReader.done) &&
|
||||
(packetWriter == null || packetWriter.done())) return;
|
||||
if (packetReader.done || packetWriter.done()) return;
|
||||
|
||||
// Report the failure outside the synchronized block, so that a thread waiting within a synchronized
|
||||
// function like connect() throws the wrapped exception.
|
||||
SmackWrappedException smackWrappedException = new SmackWrappedException(e);
|
||||
tlsHandled.reportGenericFailure(smackWrappedException);
|
||||
saslFeatureReceived.reportGenericFailure(smackWrappedException);
|
||||
maybeCompressFeaturesReceived.reportGenericFailure(smackWrappedException);
|
||||
lastFeaturesReceived.reportGenericFailure(smackWrappedException);
|
||||
|
||||
synchronized (XMPPTCPConnection.this) {
|
||||
// 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()");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* For unit testing purposes
|
||||
|
@ -934,14 +1010,13 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void afterFeaturesReceived() throws NotConnectedException, InterruptedException {
|
||||
protected void afterFeaturesReceived() throws NotConnectedException, InterruptedException, SecurityRequiredByServerException {
|
||||
StartTls startTlsFeature = getFeature(StartTls.ELEMENT, StartTls.NAMESPACE);
|
||||
if (startTlsFeature != null) {
|
||||
if (startTlsFeature.required() && config.getSecurityMode() == SecurityMode.disabled) {
|
||||
SmackException smackException = new SecurityRequiredByServerException();
|
||||
SecurityRequiredByServerException smackException = new SecurityRequiredByServerException();
|
||||
tlsHandled.reportFailure(smackException);
|
||||
notifyConnectionError(smackException);
|
||||
return;
|
||||
throw smackException;
|
||||
}
|
||||
|
||||
if (config.getSecurityMode() != ConnectionConfiguration.SecurityMode.disabled) {
|
||||
|
@ -992,6 +1067,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
|
||||
protected class PacketReader {
|
||||
|
||||
private final String threadName = "Smack Reader (" + getConnectionCounter() + ')';
|
||||
|
||||
XmlPullParser parser;
|
||||
|
||||
private volatile boolean done;
|
||||
|
@ -1006,9 +1083,15 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
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
|
||||
+ " received closing </stream> element."
|
||||
+ " Server wants to terminate the connection, calling disconnect()");
|
||||
ASYNC_BUT_ORDERED.performAsyncButOrdered(XMPPTCPConnection.this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
disconnect();
|
||||
}});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -1257,6 +1344,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
protected class PacketWriter {
|
||||
public static final int QUEUE_SIZE = XMPPTCPConnection.QUEUE_SIZE;
|
||||
|
||||
private final String threadName = "Smack Writer (" + getConnectionCounter() + ')';
|
||||
|
||||
private final ArrayBlockingQueueWithShutdown<Element> queue = new ArrayBlockingQueueWithShutdown<>(
|
||||
QUEUE_SIZE, true);
|
||||
|
||||
|
@ -1302,9 +1391,15 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
Async.go(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LOGGER.finer(threadName + " start");
|
||||
try {
|
||||
writePackets();
|
||||
} finally {
|
||||
LOGGER.finer(threadName + " exit");
|
||||
XMPPTCPConnection.this.readerWriterSemaphore.release();
|
||||
}
|
||||
}, "Smack Writer (" + getConnectionCounter() + ")");
|
||||
}
|
||||
}, threadName);
|
||||
}
|
||||
|
||||
private boolean done() {
|
||||
|
@ -1355,13 +1450,14 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
instantShutdown = instant;
|
||||
queue.shutdown();
|
||||
shutdownTimestamp = System.currentTimeMillis();
|
||||
if (shutdownDone.isNotInInitialState()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe return the next available element from the queue for writing. If the queue is shut down <b>or</b> a
|
||||
|
@ -1514,7 +1610,16 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
private void drainWriterQueueToUnacknowledgedStanzas() {
|
||||
List<Element> elements = new ArrayList<>(queue.size());
|
||||
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) {
|
||||
unacknowledgedStanzas.add((Stanza) element);
|
||||
}
|
||||
|
@ -1719,6 +1824,32 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
|||
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.
|
||||
* <p>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2014 Florian Schmaus
|
||||
* Copyright 2014-2019 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -49,11 +49,9 @@ public class PacketWriterTest {
|
|||
@Test
|
||||
public void shouldBlockAndUnblockTest() throws InterruptedException, BrokenBarrierException, NotConnectedException, XmppStringprepException {
|
||||
XMPPTCPConnection connection = new XMPPTCPConnection("user", "pass", "example.org");
|
||||
final PacketWriter pw = connection.new PacketWriter();
|
||||
connection.packetWriter = pw;
|
||||
connection.packetReader = connection.new PacketReader();
|
||||
final PacketWriter pw = connection.packetWriter;
|
||||
connection.setWriter(new BlockingStringWriter());
|
||||
pw.init();
|
||||
connection.packetWriter.init();
|
||||
|
||||
for (int i = 0; i < XMPPTCPConnection.PacketWriter.QUEUE_SIZE; i++) {
|
||||
pw.sendStreamElement(new Message());
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
allprojects {
|
||||
ext {
|
||||
shortVersion = '4.3.1'
|
||||
shortVersion = '4.3.4'
|
||||
isSnapshot = true
|
||||
jxmppVersion = '0.6.3'
|
||||
miniDnsVersion = '0.3.2'
|
||||
// When using dynamic versions for those, do *not* use [1.0,
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue