mirror of
https://github.com/vanitasvitae/Smack.git
synced 2025-09-14 11:39:39 +02:00
Compare commits
30 commits
ccc785062e
...
d74f19e26a
Author | SHA1 | Date | |
---|---|---|---|
|
d74f19e26a | ||
|
b6be7a9694 | ||
|
3f9ca68134 | ||
|
18c2c37ad0 | ||
|
9d6665735f | ||
|
c689bef7ec | ||
|
cf1f533fff | ||
|
fd1b49ca8f | ||
|
843c8dd7fe | ||
|
8e337d7810 | ||
|
f1319d1c8b | ||
|
6a617af158 | ||
|
1d49de6b60 | ||
|
afbe833a97 | ||
|
ccbc0922ad | ||
|
e2a196fa52 | ||
|
6c84356278 | ||
|
dd4cd8cede | ||
|
2900cc2274 | ||
|
7d129d6f6c | ||
|
b7465e8200 | ||
|
84b7adb764 | ||
|
22baa74298 | ||
|
81f10b0c5b | ||
|
57961a8cc1 | ||
|
d639e0bc4c | ||
|
b1a4ccfae8 | ||
|
223d58527b | ||
|
eae8acb856 | ||
|
10c6e5d121 |
90 changed files with 841 additions and 1161 deletions
|
@ -662,6 +662,14 @@ task omemoSignalIntTest {
|
||||||
dependsOn project(':smack-omemo-signal-integration-test').tasks.run
|
dependsOn project(':smack-omemo-signal-integration-test').tasks.run
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task sinttestAll {
|
||||||
|
description 'Run all of Smack\'s integration tests.'
|
||||||
|
dependsOn {[
|
||||||
|
integrationTest,
|
||||||
|
omemoSignalIntTest,
|
||||||
|
]}
|
||||||
|
}
|
||||||
|
|
||||||
def getGitCommit() {
|
def getGitCommit() {
|
||||||
def dotGit = new File("$projectDir/.git")
|
def dotGit = new File("$projectDir/.git")
|
||||||
if (!dotGit.isDirectory()) return 'non-git build'
|
if (!dotGit.isDirectory()) return 'non-git build'
|
||||||
|
|
|
@ -111,6 +111,7 @@ import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jivesoftware.smack.util.ParserUtils;
|
import org.jivesoftware.smack.util.ParserUtils;
|
||||||
import org.jivesoftware.smack.util.Predicate;
|
import org.jivesoftware.smack.util.Predicate;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
import org.jivesoftware.smack.util.Supplier;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
|
||||||
|
@ -174,6 +175,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
SmackConfiguration.getVersion();
|
SmackConfiguration.getVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected enum SyncPointState {
|
||||||
|
initial,
|
||||||
|
request_sent,
|
||||||
|
successful,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of ConnectionListeners which listen for connection closing
|
* A collection of ConnectionListeners which listen for connection closing
|
||||||
* and reconnection events.
|
* and reconnection events.
|
||||||
|
@ -271,30 +278,29 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
*/
|
*/
|
||||||
protected Writer writer;
|
protected Writer writer;
|
||||||
|
|
||||||
protected final SynchronizationPoint<SmackException> tlsHandled = new SynchronizationPoint<>(this, "establishing TLS");
|
protected SmackException currentSmackException;
|
||||||
|
protected XMPPException currentXmppException;
|
||||||
|
|
||||||
|
protected boolean tlsHandled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to success if the last features stanza from the server has been parsed. A XMPP connection
|
* Set to <code>true</code> if the last features stanza from the server has been parsed. A XMPP connection
|
||||||
* handshake can invoke multiple features stanzas, e.g. when TLS is activated a second feature
|
* handshake can invoke multiple features stanzas, e.g. when TLS is activated a second feature
|
||||||
* stanza is send by the server. This is set to true once the last feature stanza has been
|
* stanza is send by the server. This is set to true once the last feature stanza has been
|
||||||
* parsed.
|
* parsed.
|
||||||
*/
|
*/
|
||||||
protected final SynchronizationPoint<SmackException> lastFeaturesReceived = new SynchronizationPoint<>(
|
protected boolean lastFeaturesReceived;
|
||||||
AbstractXMPPConnection.this, "last stream features received from server");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to success if the SASL feature has been received.
|
* Set to <code>true</code> if the SASL feature has been received.
|
||||||
*/
|
*/
|
||||||
protected final SynchronizationPoint<XMPPException> saslFeatureReceived = new SynchronizationPoint<>(
|
protected boolean saslFeatureReceived;
|
||||||
AbstractXMPPConnection.this, "SASL mechanisms stream feature from server");
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A synchronization point which is successful if this connection has received the closing
|
* A synchronization point which is successful if this connection has received the closing
|
||||||
* stream element from the remote end-point, i.e. the server.
|
* stream element from the remote end-point, i.e. the server.
|
||||||
*/
|
*/
|
||||||
protected final SynchronizationPoint<Exception> closingStreamReceived = new SynchronizationPoint<>(
|
protected boolean closingStreamReceived;
|
||||||
this, "stream closing element received");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The SASLAuthentication manager that is responsible for authenticating with the server.
|
* The SASLAuthentication manager that is responsible for authenticating with the server.
|
||||||
|
@ -369,8 +375,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
*/
|
*/
|
||||||
protected boolean wasAuthenticated = false;
|
protected boolean wasAuthenticated = false;
|
||||||
|
|
||||||
protected Exception currentConnectionException;
|
|
||||||
|
|
||||||
private final Map<QName, IQRequestHandler> setIqRequestHandler = new HashMap<>();
|
private final Map<QName, IQRequestHandler> setIqRequestHandler = new HashMap<>();
|
||||||
private final Map<QName, IQRequestHandler> getIqRequestHandler = new HashMap<>();
|
private final Map<QName, IQRequestHandler> getIqRequestHandler = new HashMap<>();
|
||||||
|
|
||||||
|
@ -486,10 +490,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
public abstract boolean isUsingCompression();
|
public abstract boolean isUsingCompression();
|
||||||
|
|
||||||
protected void initState() {
|
protected void initState() {
|
||||||
saslFeatureReceived.init();
|
currentSmackException = null;
|
||||||
lastFeaturesReceived.init();
|
currentXmppException = null;
|
||||||
tlsHandled.init();
|
saslFeatureReceived = lastFeaturesReceived = tlsHandled = false;
|
||||||
// TODO: We do not init() closingStreamReceived here, as the integration tests use it to check if we waited for
|
// TODO: We do not init closingStreamReceived here, as the integration tests use it to check if we waited for
|
||||||
// it.
|
// it.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,7 +516,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
|
|
||||||
// Reset the connection state
|
// Reset the connection state
|
||||||
initState();
|
initState();
|
||||||
closingStreamReceived.init();
|
closingStreamReceived = false;
|
||||||
streamId = null;
|
streamId = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -657,15 +661,82 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
return streamId;
|
return streamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Resourcepart bindResourceAndEstablishSession(Resourcepart resource) throws XMPPErrorException,
|
protected final void throwCurrentConnectionException() throws SmackException, XMPPException {
|
||||||
SmackException, InterruptedException {
|
if (currentSmackException != null) {
|
||||||
|
throw currentSmackException;
|
||||||
|
} else if (currentXmppException != null) {
|
||||||
|
throw currentXmppException;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssertionError("No current connection exception set, although throwCurrentException() was called");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean hasCurrentConnectionException() {
|
||||||
|
return currentSmackException != null || currentXmppException != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void setCurrentConnectionExceptionAndNotify(Exception exception) {
|
||||||
|
if (exception instanceof SmackException) {
|
||||||
|
currentSmackException = (SmackException) exception;
|
||||||
|
} else if (exception instanceof XMPPException) {
|
||||||
|
currentXmppException = (XMPPException) exception;
|
||||||
|
} else {
|
||||||
|
currentSmackException = new SmackException.SmackWrappedException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyWaitingThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We use an extra object for {@link #notifyWaitingThreads()} and {@link #waitForCondition(Supplier)}, because all state
|
||||||
|
* changing methods of the connection are synchronized using the connection instance as monitor. If we now would
|
||||||
|
* also use the connection instance for the internal process to wait for a condition, the {@link Object#wait()}
|
||||||
|
* would leave the monitor when it waites, which would allow for another potential call to a state changing function
|
||||||
|
* to proceed.
|
||||||
|
*/
|
||||||
|
private final Object internalMonitor = new Object();
|
||||||
|
|
||||||
|
protected final void notifyWaitingThreads() {
|
||||||
|
synchronized (internalMonitor) {
|
||||||
|
internalMonitor.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final boolean waitForCondition(Supplier<Boolean> condition) throws InterruptedException {
|
||||||
|
final long deadline = System.currentTimeMillis() + getReplyTimeout();
|
||||||
|
synchronized (internalMonitor) {
|
||||||
|
while (!condition.get().booleanValue() && !hasCurrentConnectionException()) {
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
if (now >= deadline) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
internalMonitor.wait(deadline - now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void waitForCondition(Supplier<Boolean> condition, String waitFor) throws InterruptedException, NoResponseException {
|
||||||
|
boolean success = waitForCondition(condition);
|
||||||
|
if (!success) {
|
||||||
|
throw NoResponseException.newWith(this, waitFor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void waitForConditionOrThrowConnectionException(Supplier<Boolean> condition, String waitFor) throws InterruptedException, SmackException, XMPPException {
|
||||||
|
waitForCondition(condition, waitFor);
|
||||||
|
if (hasCurrentConnectionException()) {
|
||||||
|
throwCurrentConnectionException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Resourcepart bindResourceAndEstablishSession(Resourcepart resource)
|
||||||
|
throws SmackException, InterruptedException, XMPPException {
|
||||||
// Wait until either:
|
// Wait until either:
|
||||||
// - the servers last features stanza has been parsed
|
// - the servers last features stanza has been parsed
|
||||||
// - the timeout occurs
|
// - the timeout occurs
|
||||||
LOGGER.finer("Waiting for last features to be received before continuing with resource binding");
|
LOGGER.finer("Waiting for last features to be received before continuing with resource binding");
|
||||||
lastFeaturesReceived.checkIfSuccessOrWaitOrThrow();
|
waitForConditionOrThrowConnectionException(() -> lastFeaturesReceived, "last stream features received from server");
|
||||||
|
|
||||||
|
|
||||||
if (!hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
|
if (!hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
|
||||||
// Server never offered resource binding, which is REQUIRED in XMPP client and
|
// Server never offered resource binding, which is REQUIRED in XMPP client and
|
||||||
|
@ -892,6 +963,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
callConnectionClosedListener();
|
callConnectionClosedListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Object notifyConnectionErrorMonitor = new Object();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends out a notification that there was an error with the connection
|
* Sends out a notification that there was an error with the connection
|
||||||
* and closes the connection.
|
* and closes the connection.
|
||||||
|
@ -899,41 +972,31 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
* @param exception the exception that causes the connection close event.
|
* @param exception the exception that causes the connection close event.
|
||||||
*/
|
*/
|
||||||
protected final void notifyConnectionError(final Exception exception) {
|
protected final void notifyConnectionError(final Exception exception) {
|
||||||
|
synchronized (notifyConnectionErrorMonitor) {
|
||||||
if (!isConnected()) {
|
if (!isConnected()) {
|
||||||
LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception,
|
LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception,
|
||||||
exception);
|
exception);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ASYNC_BUT_ORDERED.performAsyncButOrdered(this, () -> {
|
// Note that we first have to set the current connection exception and notify waiting threads, as one of them
|
||||||
currentConnectionException = exception;
|
// could hold the instance lock, which we also need later when calling instantShutdown().
|
||||||
|
setCurrentConnectionExceptionAndNotify(exception);
|
||||||
for (StanzaCollector collector : collectors) {
|
|
||||||
collector.notifyConnectionError(exception);
|
|
||||||
}
|
|
||||||
SmackWrappedException smackWrappedException = new SmackWrappedException(exception);
|
|
||||||
tlsHandled.reportGenericFailure(smackWrappedException);
|
|
||||||
saslFeatureReceived.reportGenericFailure(smackWrappedException);
|
|
||||||
lastFeaturesReceived.reportGenericFailure(smackWrappedException);
|
|
||||||
closingStreamReceived.reportFailure(smackWrappedException);
|
|
||||||
// TODO From XMPPTCPConnection. Was called in Smack 4.3 where notifyConnectionError() was part of
|
|
||||||
// XMPPTCPConnection. Create delegation method?
|
|
||||||
// maybeCompressFeaturesReceived.reportGenericFailure(smackWrappedException);
|
|
||||||
|
|
||||||
synchronized (AbstractXMPPConnection.this) {
|
|
||||||
notifyAll();
|
|
||||||
|
|
||||||
// Closes the connection temporary. A if the connection supports stream management, then a reconnection is
|
// Closes the connection temporary. A if the connection supports stream management, then a reconnection is
|
||||||
// possible. Note that a connection listener of e.g. XMPPTCPConnection will drop the SM state in
|
// possible. Note that a connection listener of e.g. XMPPTCPConnection will drop the SM state in
|
||||||
// case the Exception is a StreamErrorException.
|
// case the Exception is a StreamErrorException.
|
||||||
instantShutdown();
|
instantShutdown();
|
||||||
|
|
||||||
|
for (StanzaCollector collector : collectors) {
|
||||||
|
collector.notifyConnectionError(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
Async.go(() -> {
|
Async.go(() -> {
|
||||||
// Notify connection listeners of the error.
|
// Notify connection listeners of the error.
|
||||||
callConnectionClosedOnErrorListener(exception);
|
callConnectionClosedOnErrorListener(exception);
|
||||||
}, AbstractXMPPConnection.this + " callConnectionClosedOnErrorListener()");
|
}, AbstractXMPPConnection.this + " callConnectionClosedOnErrorListener()");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -947,19 +1010,13 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
protected abstract void shutdown();
|
protected abstract void shutdown();
|
||||||
|
|
||||||
protected final boolean waitForClosingStreamTagFromServer() {
|
protected final boolean waitForClosingStreamTagFromServer() {
|
||||||
Exception exception;
|
|
||||||
try {
|
try {
|
||||||
// After we send the closing stream element, check if there was already a
|
waitForConditionOrThrowConnectionException(() -> closingStreamReceived, "closing stream tag from the server");
|
||||||
// closing stream element sent by the server or wait with a timeout for a
|
} catch (InterruptedException | SmackException | XMPPException e) {
|
||||||
// closing stream element to be received from the server.
|
LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, e);
|
||||||
exception = closingStreamReceived.checkIfSuccessOrWait();
|
return false;
|
||||||
} catch (InterruptedException | NoResponseException e) {
|
|
||||||
exception = e;
|
|
||||||
}
|
}
|
||||||
if (exception != null) {
|
return true;
|
||||||
LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, exception);
|
|
||||||
}
|
|
||||||
return exception == null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1817,8 +1874,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
// Only proceed with SASL auth if TLS is disabled or if the server doesn't announce it
|
// Only proceed with SASL auth if TLS is disabled or if the server doesn't announce it
|
||||||
if (!hasFeature(StartTls.ELEMENT, StartTls.NAMESPACE)
|
if (!hasFeature(StartTls.ELEMENT, StartTls.NAMESPACE)
|
||||||
|| config.getSecurityMode() == SecurityMode.disabled) {
|
|| config.getSecurityMode() == SecurityMode.disabled) {
|
||||||
tlsHandled.reportSuccess();
|
tlsHandled = saslFeatureReceived = true;
|
||||||
saslFeatureReceived.reportSuccess();
|
notifyWaitingThreads();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1827,9 +1884,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
if (hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
|
if (hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
|
||||||
if (!hasFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE)
|
if (!hasFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE)
|
||||||
|| !config.isCompressionEnabled()) {
|
|| !config.isCompressionEnabled()) {
|
||||||
// This was was last features from the server is either it did not contain
|
// This where the last stream features from the server, either it did not contain
|
||||||
// compression or if we disabled it
|
// compression or we disabled it.
|
||||||
lastFeaturesReceived.reportSuccess();
|
lastFeaturesReceived = true;
|
||||||
|
notifyWaitingThreads();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
afterFeaturesReceived();
|
afterFeaturesReceived();
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack;
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -371,15 +372,6 @@ public abstract class SmackException extends Exception {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ConnectionUnexpectedTerminatedException extends SmackException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
public ConnectionUnexpectedTerminatedException(Throwable wrappedThrowable) {
|
|
||||||
super(wrappedThrowable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class FeatureNotSupportedException extends SmackException {
|
public static class FeatureNotSupportedException extends SmackException {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -487,4 +479,19 @@ public abstract class SmackException extends Exception {
|
||||||
super(message, exception);
|
super(message, exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SmackCertificateException extends SmackException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final CertificateException certificateException;
|
||||||
|
|
||||||
|
public SmackCertificateException(CertificateException certificateException) {
|
||||||
|
this.certificateException = certificateException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CertificateException getCertificateException() {
|
||||||
|
return certificateException;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package org.jivesoftware.smack;
|
package org.jivesoftware.smack;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.nio.channels.CancelledKeyException;
|
import java.nio.channels.CancelledKeyException;
|
||||||
import java.nio.channels.ClosedChannelException;
|
import java.nio.channels.ClosedChannelException;
|
||||||
import java.nio.channels.SelectableChannel;
|
import java.nio.channels.SelectableChannel;
|
||||||
|
@ -368,14 +367,9 @@ public class SmackReactor {
|
||||||
for (SelectionKey selectionKey : selectedKeys) {
|
for (SelectionKey selectionKey : selectedKeys) {
|
||||||
SelectableChannel channel = selectionKey.channel();
|
SelectableChannel channel = selectionKey.channel();
|
||||||
SelectionKeyAttachment selectionKeyAttachment = (SelectionKeyAttachment) selectionKey.attachment();
|
SelectionKeyAttachment selectionKeyAttachment = (SelectionKeyAttachment) selectionKey.attachment();
|
||||||
ChannelSelectedCallback channelSelectedCallback = selectionKeyAttachment.weaeklyReferencedChannelSelectedCallback.get();
|
ChannelSelectedCallback channelSelectedCallback = selectionKeyAttachment.channelSelectedCallback;
|
||||||
if (channelSelectedCallback != null) {
|
|
||||||
channelSelectedCallback.onChannelSelected(channel, selectionKey);
|
channelSelectedCallback.onChannelSelected(channel, selectionKey);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
selectionKey.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ChannelSelectedCallback {
|
public interface ChannelSelectedCallback {
|
||||||
|
@ -422,11 +416,11 @@ public class SmackReactor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class SelectionKeyAttachment {
|
public static final class SelectionKeyAttachment {
|
||||||
private final WeakReference<ChannelSelectedCallback> weaeklyReferencedChannelSelectedCallback;
|
private final ChannelSelectedCallback channelSelectedCallback;
|
||||||
private final AtomicBoolean reactorThreadRacing = new AtomicBoolean();
|
private final AtomicBoolean reactorThreadRacing = new AtomicBoolean();
|
||||||
|
|
||||||
private SelectionKeyAttachment(ChannelSelectedCallback channelSelectedCallback) {
|
private SelectionKeyAttachment(ChannelSelectedCallback channelSelectedCallback) {
|
||||||
this.weaeklyReferencedChannelSelectedCallback = new WeakReference<>(channelSelectedCallback);
|
this.channelSelectedCallback = channelSelectedCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setRacing() {
|
private void setRacing() {
|
||||||
|
|
|
@ -1,352 +0,0 @@
|
||||||
/**
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* 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 java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.locks.Condition;
|
|
||||||
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;
|
|
||||||
|
|
||||||
public class SynchronizationPoint<E extends Exception> {
|
|
||||||
|
|
||||||
private final AbstractXMPPConnection connection;
|
|
||||||
private final Lock connectionLock;
|
|
||||||
private final Condition condition;
|
|
||||||
private final String waitFor;
|
|
||||||
|
|
||||||
// Note that there is no need to make 'state' and 'failureException' volatile. Since 'lock' and 'unlock' have the
|
|
||||||
// same memory synchronization effects as synchronization block enter and leave.
|
|
||||||
private State state;
|
|
||||||
private E failureException;
|
|
||||||
private SmackWrappedException smackWrappedExcpetion;
|
|
||||||
|
|
||||||
private volatile long waitStart;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a new synchronization point for the given connection.
|
|
||||||
*
|
|
||||||
* @param connection the connection of this synchronization point.
|
|
||||||
* @param waitFor a description of the event this synchronization point handles.
|
|
||||||
*/
|
|
||||||
public SynchronizationPoint(AbstractXMPPConnection connection, String waitFor) {
|
|
||||||
this.connection = connection;
|
|
||||||
this.connectionLock = connection.getConnectionLock();
|
|
||||||
this.condition = connection.getConnectionLock().newCondition();
|
|
||||||
this.waitFor = waitFor;
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize (or reset) this synchronization point.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("LockNotBeforeTry")
|
|
||||||
public void init() {
|
|
||||||
connectionLock.lock();
|
|
||||||
state = State.Initial;
|
|
||||||
failureException = null;
|
|
||||||
smackWrappedExcpetion = null;
|
|
||||||
connectionLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the given top level stream element and wait for a response.
|
|
||||||
*
|
|
||||||
* @param request the plain stream element to send.
|
|
||||||
* @throws NoResponseException if no response was received.
|
|
||||||
* @throws NotConnectedException if the connection is not connected.
|
|
||||||
* @throws InterruptedException if the connection is interrupted.
|
|
||||||
* @return <code>null</code> if synchronization point was successful, or the failure Exception.
|
|
||||||
*/
|
|
||||||
public Exception sendAndWaitForResponse(TopLevelStreamElement request) throws NoResponseException,
|
|
||||||
NotConnectedException, InterruptedException {
|
|
||||||
assert state == State.Initial;
|
|
||||||
connectionLock.lock();
|
|
||||||
try {
|
|
||||||
if (request != null) {
|
|
||||||
if (request instanceof Stanza) {
|
|
||||||
connection.sendStanza((Stanza) request);
|
|
||||||
}
|
|
||||||
else if (request instanceof Nonza) {
|
|
||||||
connection.sendNonza((Nonza) request);
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Unsupported element type");
|
|
||||||
}
|
|
||||||
state = State.RequestSent;
|
|
||||||
}
|
|
||||||
waitForConditionOrTimeout();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
connectionLock.unlock();
|
|
||||||
}
|
|
||||||
return checkForResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the given plain stream element and wait for a response.
|
|
||||||
*
|
|
||||||
* @param request the plain stream element to send.
|
|
||||||
* @throws E if an failure was reported.
|
|
||||||
* @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, SmackWrappedException {
|
|
||||||
sendAndWaitForResponse(request);
|
|
||||||
switch (state) {
|
|
||||||
case Failure:
|
|
||||||
throwException();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Success, do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this synchronization point is successful or wait the connections reply timeout.
|
|
||||||
* @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, SmackWrappedException {
|
|
||||||
checkIfSuccessOrWait();
|
|
||||||
if (state == State.Failure) {
|
|
||||||
throwException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this synchronization point is successful or wait the connections reply timeout.
|
|
||||||
* @throws NoResponseException if there was no response marking the synchronization point as success or failed.
|
|
||||||
* @throws InterruptedException if the calling thread was interrupted.
|
|
||||||
* @return <code>null</code> if synchronization point was successful, or the failure Exception.
|
|
||||||
*/
|
|
||||||
public Exception checkIfSuccessOrWait() throws NoResponseException, InterruptedException {
|
|
||||||
connectionLock.lock();
|
|
||||||
try {
|
|
||||||
switch (state) {
|
|
||||||
// Return immediately on success or failure
|
|
||||||
case Success:
|
|
||||||
return null;
|
|
||||||
case Failure:
|
|
||||||
return getException();
|
|
||||||
default:
|
|
||||||
// Do nothing
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
waitForConditionOrTimeout();
|
|
||||||
} finally {
|
|
||||||
connectionLock.unlock();
|
|
||||||
}
|
|
||||||
return checkForResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Report this synchronization point as successful.
|
|
||||||
*/
|
|
||||||
public void reportSuccess() {
|
|
||||||
connectionLock.lock();
|
|
||||||
try {
|
|
||||||
state = State.Success;
|
|
||||||
condition.signalAll();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
connectionLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deprecated.
|
|
||||||
* @deprecated use {@link #reportFailure(Exception)} instead.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void reportFailure() {
|
|
||||||
reportFailure(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Report this synchronization point as failed because of the given exception. The {@code failureException} must be set.
|
|
||||||
*
|
|
||||||
* @param failureException the exception causing this synchronization point to fail.
|
|
||||||
*/
|
|
||||||
public void reportFailure(E failureException) {
|
|
||||||
assert failureException != null;
|
|
||||||
connectionLock.lock();
|
|
||||||
try {
|
|
||||||
state = State.Failure;
|
|
||||||
this.failureException = failureException;
|
|
||||||
condition.signalAll();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
connectionLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* @return true if the synchronization point was successful, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean wasSuccessful() {
|
|
||||||
connectionLock.lock();
|
|
||||||
try {
|
|
||||||
return state == State.Success;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
connectionLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNotInInitialState() {
|
|
||||||
connectionLock.lock();
|
|
||||||
try {
|
|
||||||
return state != State.Initial;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
connectionLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this synchronization point has its request already sent.
|
|
||||||
*
|
|
||||||
* @return true if the request was already sent, false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean requestSent() {
|
|
||||||
connectionLock.lock();
|
|
||||||
try {
|
|
||||||
return state == State.RequestSent;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
connectionLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public E getFailureException() {
|
|
||||||
connectionLock.lock();
|
|
||||||
try {
|
|
||||||
return failureException;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
connectionLock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetTimeout() {
|
|
||||||
waitStart = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait for the condition to become something else as {@link State#RequestSent} or {@link State#Initial}.
|
|
||||||
* {@link #reportSuccess()}, {@link #reportFailure()} and {@link #reportFailure(Exception)} will either set this
|
|
||||||
* synchronization point to {@link State#Success} or {@link State#Failure}. If none of them is set after the
|
|
||||||
* connections reply timeout, this method will set the state of {@link State#NoResponse}.
|
|
||||||
* @throws InterruptedException if the calling thread was interrupted.
|
|
||||||
*/
|
|
||||||
private void waitForConditionOrTimeout() throws InterruptedException {
|
|
||||||
waitStart = System.currentTimeMillis();
|
|
||||||
while (state == State.RequestSent || state == State.Initial) {
|
|
||||||
long timeout = connection.getReplyTimeout();
|
|
||||||
long remainingWaitMillis = timeout - (System.currentTimeMillis() - waitStart);
|
|
||||||
long remainingWait = TimeUnit.MILLISECONDS.toNanos(remainingWaitMillis);
|
|
||||||
|
|
||||||
if (remainingWait <= 0) {
|
|
||||||
state = State.NoResponse;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
condition.awaitNanos(remainingWait);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
state = State.Interrupted;
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
* The exception is thrown, if state is one of 'Initial', 'NoResponse' or 'RequestSent'
|
|
||||||
* </p>
|
|
||||||
* @return <code>true</code> if synchronization point was successful, <code>false</code> on failure.
|
|
||||||
* @throws NoResponseException if there was no response from the remote entity.
|
|
||||||
*/
|
|
||||||
private Exception checkForResponse() throws NoResponseException {
|
|
||||||
switch (state) {
|
|
||||||
case Initial:
|
|
||||||
case NoResponse:
|
|
||||||
case RequestSent:
|
|
||||||
throw NoResponseException.newWith(connection, waitFor);
|
|
||||||
case Success:
|
|
||||||
return null;
|
|
||||||
case Failure:
|
|
||||||
return getException();
|
|
||||||
default:
|
|
||||||
throw new AssertionError("Unknown state " + state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum State {
|
|
||||||
Initial,
|
|
||||||
RequestSent,
|
|
||||||
NoResponse,
|
|
||||||
Success,
|
|
||||||
Failure,
|
|
||||||
Interrupted,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -67,7 +67,8 @@ public interface XmppInputOutputFilter {
|
||||||
default void closeInputOutput() {
|
default void closeInputOutput() {
|
||||||
}
|
}
|
||||||
|
|
||||||
default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException, InterruptedException, SmackException {
|
default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException,
|
||||||
|
InterruptedException, SmackException, XMPPException {
|
||||||
}
|
}
|
||||||
|
|
||||||
Object getStats();
|
Object getStats();
|
||||||
|
|
|
@ -35,7 +35,6 @@ import javax.net.ssl.SSLSession;
|
||||||
|
|
||||||
import org.jivesoftware.smack.AbstractXMPPConnection;
|
import org.jivesoftware.smack.AbstractXMPPConnection;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
|
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.SmackFuture;
|
import org.jivesoftware.smack.SmackFuture;
|
||||||
|
@ -78,6 +77,7 @@ import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
|
||||||
import org.jivesoftware.smack.util.ExtendedAppendable;
|
import org.jivesoftware.smack.util.ExtendedAppendable;
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
import org.jivesoftware.smack.util.Supplier;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
|
||||||
|
@ -142,7 +142,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStreamClosed() {
|
public void onStreamClosed() {
|
||||||
ModularXmppClientToServerConnection.this.closingStreamReceived.reportSuccess();
|
ModularXmppClientToServerConnection.this.closingStreamReceived = true;
|
||||||
|
notifyWaitingThreads();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -177,7 +178,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
||||||
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
|
SmackException, XMPPException {
|
||||||
ModularXmppClientToServerConnection.this.newStreamOpenWaitForFeaturesSequence(waitFor);
|
ModularXmppClientToServerConnection.this.newStreamOpenWaitForFeaturesSequence(waitFor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,8 +199,14 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Exception getCurrentConnectionException() {
|
public void waitForCondition(Supplier<Boolean> condition, String waitFor)
|
||||||
return ModularXmppClientToServerConnection.this.currentConnectionException;
|
throws InterruptedException, SmackException, XMPPException {
|
||||||
|
ModularXmppClientToServerConnection.this.waitForConditionOrThrowConnectionException(condition, waitFor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyWaitingThreads() {
|
||||||
|
ModularXmppClientToServerConnection.this.notifyWaitingThreads();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -263,14 +270,13 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
revertedState.resetState();
|
revertedState.resetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void walkStateGraph(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
|
protected void walkStateGraph(WalkStateGraphContext walkStateGraphContext)
|
||||||
SASLErrorException, FailedNonzaException, IOException, SmackException, InterruptedException {
|
throws XMPPException, IOException, SmackException, InterruptedException {
|
||||||
// Save a copy of the current state
|
// Save a copy of the current state
|
||||||
GraphVertex<State> previousStateVertex = currentStateVertex;
|
GraphVertex<State> previousStateVertex = currentStateVertex;
|
||||||
try {
|
try {
|
||||||
walkStateGraphInternal(walkStateGraphContext);
|
walkStateGraphInternal(walkStateGraphContext);
|
||||||
} catch (XMPPErrorException | SASLErrorException | FailedNonzaException | IOException | SmackException
|
} catch (IOException | SmackException | InterruptedException | XMPPException e) {
|
||||||
| InterruptedException e) {
|
|
||||||
currentStateVertex = previousStateVertex;
|
currentStateVertex = previousStateVertex;
|
||||||
// Unwind the state.
|
// Unwind the state.
|
||||||
State revertedState = currentStateVertex.getElement();
|
State revertedState = currentStateVertex.getElement();
|
||||||
|
@ -279,8 +285,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
|
private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext)
|
||||||
SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
|
throws IOException, SmackException, InterruptedException, XMPPException {
|
||||||
// Save a copy of the current state
|
// Save a copy of the current state
|
||||||
final GraphVertex<State> initialStateVertex = currentStateVertex;
|
final GraphVertex<State> initialStateVertex = currentStateVertex;
|
||||||
final State initialState = initialStateVertex.getElement();
|
final State initialState = initialStateVertex.getElement();
|
||||||
|
@ -353,21 +359,19 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to enter a state. Note that this method may return <code>null</code> if this state can be safely ignored ignored.
|
* Attempt to enter a state. Note that this method may return <code>null</code> if this state can be safely ignored.
|
||||||
*
|
*
|
||||||
* @param successorStateVertex the successor state vertex.
|
* @param successorStateVertex the successor state vertex.
|
||||||
* @param walkStateGraphContext the "walk state graph" context.
|
* @param walkStateGraphContext the "walk state graph" context.
|
||||||
* @return A state transition result or <code>null</code> if this state can be ignored.
|
* @return A state transition result or <code>null</code> if this state can be ignored.
|
||||||
* @throws SmackException if Smack detected an exceptional situation.
|
* @throws SmackException if Smack detected an exceptional situation.
|
||||||
* @throws XMPPErrorException if an XMPP protocol error was received.
|
* @throws XMPPException if an XMPP protocol error was received.
|
||||||
* @throws SASLErrorException if a SASL protocol error was returned.
|
|
||||||
* @throws IOException if an I/O error occurred.
|
* @throws IOException if an I/O error occurred.
|
||||||
* @throws InterruptedException if the calling thread was interrupted.
|
* @throws InterruptedException if the calling thread was interrupted.
|
||||||
* @throws FailedNonzaException if an XMPP protocol failure was received.
|
|
||||||
*/
|
*/
|
||||||
private StateTransitionResult attemptEnterState(GraphVertex<State> successorStateVertex,
|
private StateTransitionResult attemptEnterState(GraphVertex<State> successorStateVertex,
|
||||||
WalkStateGraphContext walkStateGraphContext) throws SmackException, XMPPErrorException,
|
WalkStateGraphContext walkStateGraphContext) throws SmackException, XMPPException,
|
||||||
SASLErrorException, IOException, InterruptedException, FailedNonzaException {
|
IOException, InterruptedException {
|
||||||
final GraphVertex<State> initialStateVertex = currentStateVertex;
|
final GraphVertex<State> initialStateVertex = currentStateVertex;
|
||||||
final State initialState = initialStateVertex.getElement();
|
final State initialState = initialStateVertex.getElement();
|
||||||
final State successorState = successorStateVertex.getElement();
|
final State successorState = successorStateVertex.getElement();
|
||||||
|
@ -400,8 +404,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
|
|
||||||
invokeConnectionStateMachineListener(new ConnectionStateEvent.AboutToTransitionInto(initialState, successorState));
|
invokeConnectionStateMachineListener(new ConnectionStateEvent.AboutToTransitionInto(initialState, successorState));
|
||||||
transitionAttemptResult = successorState.transitionInto(walkStateGraphContext);
|
transitionAttemptResult = successorState.transitionInto(walkStateGraphContext);
|
||||||
} catch (SmackException | XMPPErrorException | SASLErrorException | IOException | InterruptedException
|
} catch (SmackException | IOException | InterruptedException | XMPPException e) {
|
||||||
| FailedNonzaException e) {
|
|
||||||
// Unwind the state here too, since this state will not be unwound by walkStateGraph(), as it will not
|
// Unwind the state here too, since this state will not be unwound by walkStateGraph(), as it will not
|
||||||
// become a predecessor state in the walk.
|
// become a predecessor state in the walk.
|
||||||
unwindState(successorState);
|
unwindState(successorState);
|
||||||
|
@ -474,8 +477,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
|
|
||||||
try {
|
try {
|
||||||
walkStateGraph(context);
|
walkStateGraph(context);
|
||||||
} catch (XMPPErrorException | SASLErrorException | IOException | SmackException | InterruptedException
|
} catch (IOException | SmackException | InterruptedException | XMPPException e) {
|
||||||
| FailedNonzaException e) {
|
|
||||||
throw new IllegalStateException("A walk to disconnected state should never throw", e);
|
throw new IllegalStateException("A walk to disconnected state should never throw", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -491,9 +493,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
@Override
|
@Override
|
||||||
protected void afterFeaturesReceived() {
|
protected void afterFeaturesReceived() {
|
||||||
featuresReceived = true;
|
featuresReceived = true;
|
||||||
synchronized (this) {
|
notifyWaitingThreads();
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void parseAndProcessElement(String element) {
|
protected void parseAndProcessElement(String element) {
|
||||||
|
@ -522,8 +522,10 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
break;
|
break;
|
||||||
case "error":
|
case "error":
|
||||||
StreamError streamError = PacketParserUtils.parseStreamError(parser, null);
|
StreamError streamError = PacketParserUtils.parseStreamError(parser, null);
|
||||||
saslFeatureReceived.reportFailure(new StreamErrorException(streamError));
|
StreamErrorException streamErrorException = new StreamErrorException(streamError);
|
||||||
throw new StreamErrorException(streamError);
|
currentXmppException = streamErrorException;
|
||||||
|
notifyWaitingThreads();
|
||||||
|
throw streamErrorException;
|
||||||
case "features":
|
case "features":
|
||||||
parseFeatures(parser);
|
parseFeatures(parser);
|
||||||
afterFeaturesReceived();
|
afterFeaturesReceived();
|
||||||
|
@ -550,25 +552,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void waitForFeaturesReceived(String waitFor)
|
protected void waitForFeaturesReceived(String waitFor)
|
||||||
throws InterruptedException, ConnectionUnexpectedTerminatedException, NoResponseException {
|
throws InterruptedException, SmackException, XMPPException {
|
||||||
long waitStartMs = System.currentTimeMillis();
|
waitForConditionOrThrowConnectionException(() -> featuresReceived, waitFor);
|
||||||
long timeoutMs = getReplyTimeout();
|
|
||||||
synchronized (this) {
|
|
||||||
while (!featuresReceived && currentConnectionException == null) {
|
|
||||||
long remainingWaitMs = timeoutMs - (System.currentTimeMillis() - waitStartMs);
|
|
||||||
if (remainingWaitMs <= 0) {
|
|
||||||
throw NoResponseException.newWith(this, waitFor);
|
|
||||||
}
|
|
||||||
wait(remainingWaitMs);
|
|
||||||
}
|
|
||||||
if (currentConnectionException != null) {
|
|
||||||
throw new SmackException.ConnectionUnexpectedTerminatedException(currentConnectionException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
protected void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
||||||
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
|
SmackException, XMPPException {
|
||||||
prepareToWaitForFeaturesReceived();
|
prepareToWaitForFeaturesReceived();
|
||||||
sendStreamOpen();
|
sendStreamOpen();
|
||||||
waitForFeaturesReceived(waitFor);
|
waitForFeaturesReceived(waitFor);
|
||||||
|
@ -763,8 +752,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
||||||
throws XMPPErrorException, SASLErrorException, IOException, SmackException,
|
throws IOException, SmackException, InterruptedException, XMPPException {
|
||||||
InterruptedException {
|
|
||||||
prepareToWaitForFeaturesReceived();
|
prepareToWaitForFeaturesReceived();
|
||||||
|
|
||||||
LoginContext loginContext = walkStateGraphContext.getLoginContext();
|
LoginContext loginContext = walkStateGraphContext.getLoginContext();
|
||||||
|
@ -813,12 +801,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
||||||
throws XMPPErrorException, SASLErrorException, IOException, SmackException,
|
throws IOException, SmackException, InterruptedException, XMPPException {
|
||||||
InterruptedException {
|
|
||||||
// Calling bindResourceAndEstablishSession() below requires the lastFeaturesReceived sync point to be signaled.
|
// Calling bindResourceAndEstablishSession() below requires the lastFeaturesReceived sync point to be signaled.
|
||||||
// Since we entered this state, the FSM has decided that the last features have been received, hence signal
|
// Since we entered this state, the FSM has decided that the last features have been received, hence signal
|
||||||
// the sync point.
|
// the sync point.
|
||||||
lastFeaturesReceived.reportSuccess();
|
lastFeaturesReceived = true;
|
||||||
|
notifyWaitingThreads();
|
||||||
|
|
||||||
LoginContext loginContext = walkStateGraphContext.getLoginContext();
|
LoginContext loginContext = walkStateGraphContext.getLoginContext();
|
||||||
Resourcepart resource = bindResourceAndEstablishSession(loginContext.resource);
|
Resourcepart resource = bindResourceAndEstablishSession(loginContext.resource);
|
||||||
|
@ -914,7 +902,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
|
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
|
||||||
closingStreamReceived.init();
|
closingStreamReceived = false;
|
||||||
|
|
||||||
boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(StreamClose.INSTANCE);
|
boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(StreamClose.INSTANCE);
|
||||||
|
|
||||||
|
@ -936,7 +924,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
XmppInputOutputFilter filter = it.next();
|
XmppInputOutputFilter filter = it.next();
|
||||||
try {
|
try {
|
||||||
filter.waitUntilInputOutputClosed();
|
filter.waitUntilInputOutputClosed();
|
||||||
} catch (IOException | CertificateException | InterruptedException | SmackException e) {
|
} catch (IOException | CertificateException | InterruptedException | SmackException | XMPPException e) {
|
||||||
LOGGER.log(Level.WARNING, "waitUntilInputOutputClosed() threw", e);
|
LOGGER.log(Level.WARNING, "waitUntilInputOutputClosed() threw", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,12 @@ import java.nio.channels.SelectionKey;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.SmackReactor;
|
import org.jivesoftware.smack.SmackReactor;
|
||||||
import org.jivesoftware.smack.SmackReactor.ChannelSelectedCallback;
|
import org.jivesoftware.smack.SmackReactor.ChannelSelectedCallback;
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
||||||
import org.jivesoftware.smack.XmppInputOutputFilter;
|
import org.jivesoftware.smack.XmppInputOutputFilter;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
|
||||||
|
@ -38,6 +39,7 @@ import org.jivesoftware.smack.packet.Nonza;
|
||||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
import org.jivesoftware.smack.util.Consumer;
|
import org.jivesoftware.smack.util.Consumer;
|
||||||
|
import org.jivesoftware.smack.util.Supplier;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
|
|
||||||
public abstract class ModularXmppClientToServerConnectionInternal {
|
public abstract class ModularXmppClientToServerConnectionInternal {
|
||||||
|
@ -98,7 +100,7 @@ public abstract class ModularXmppClientToServerConnectionInternal {
|
||||||
public abstract ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterEndIterator();
|
public abstract ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterEndIterator();
|
||||||
|
|
||||||
public abstract void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
public abstract void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
|
||||||
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException;
|
NoResponseException, NotConnectedException, SmackException, XMPPException;
|
||||||
|
|
||||||
public abstract SmackTlsContext getSmackTlsContext();
|
public abstract SmackTlsContext getSmackTlsContext();
|
||||||
|
|
||||||
|
@ -108,7 +110,9 @@ public abstract class ModularXmppClientToServerConnectionInternal {
|
||||||
|
|
||||||
public abstract void asyncGo(Runnable runnable);
|
public abstract void asyncGo(Runnable runnable);
|
||||||
|
|
||||||
public abstract Exception getCurrentConnectionException();
|
public abstract void waitForCondition(Supplier<Boolean> condition, String waitFor) throws InterruptedException, SmackException, XMPPException;
|
||||||
|
|
||||||
|
public abstract void notifyWaitingThreads();
|
||||||
|
|
||||||
public abstract void setCompressionEnabled(boolean compressionEnabled);
|
public abstract void setCompressionEnabled(boolean compressionEnabled);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2014-2015 Florian Schmaus
|
* Copyright © 2014-2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,12 +16,15 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.compress.packet;
|
package org.jivesoftware.smack.compress.packet;
|
||||||
|
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Nonza;
|
import org.jivesoftware.smack.packet.Nonza;
|
||||||
|
|
||||||
public final class Compressed implements Nonza {
|
public final class Compressed implements Nonza {
|
||||||
|
|
||||||
public static final String ELEMENT = "compressed";
|
public static final String ELEMENT = "compressed";
|
||||||
public static final String NAMESPACE = Compress.NAMESPACE;
|
public static final String NAMESPACE = Compress.NAMESPACE;
|
||||||
|
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
|
||||||
|
|
||||||
public static final Compressed INSTANCE = new Compressed();
|
public static final Compressed INSTANCE = new Compressed();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2018 Florian Schmaus
|
* Copyright 2018-2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,6 +18,8 @@ package org.jivesoftware.smack.compress.packet;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.Nonza;
|
import org.jivesoftware.smack.packet.Nonza;
|
||||||
import org.jivesoftware.smack.packet.StanzaError;
|
import org.jivesoftware.smack.packet.StanzaError;
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
@ -26,6 +28,7 @@ public class Failure implements Nonza {
|
||||||
|
|
||||||
public static final String ELEMENT = "failure";
|
public static final String ELEMENT = "failure";
|
||||||
public static final String NAMESPACE = Compress.NAMESPACE;
|
public static final String NAMESPACE = Compress.NAMESPACE;
|
||||||
|
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
|
||||||
|
|
||||||
public enum CompressFailureError {
|
public enum CompressFailureError {
|
||||||
setup_failed,
|
setup_failed,
|
||||||
|
|
|
@ -17,10 +17,8 @@
|
||||||
package org.jivesoftware.smack.compression;
|
package org.jivesoftware.smack.compression;
|
||||||
|
|
||||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||||
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
|
||||||
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
|
||||||
import org.jivesoftware.smack.XmppInputOutputFilter;
|
import org.jivesoftware.smack.XmppInputOutputFilter;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedButUnboundStateDescriptor;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedButUnboundStateDescriptor;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ResourceBindingStateDescriptor;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ResourceBindingStateDescriptor;
|
||||||
|
@ -90,8 +88,7 @@ public class CompressionModule extends ModularXmppClientToServerConnectionModule
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
||||||
throws NoResponseException, NotConnectedException, FailedNonzaException, InterruptedException,
|
throws InterruptedException, SmackException, XMPPException {
|
||||||
ConnectionUnexpectedTerminatedException {
|
|
||||||
final String compressionMethod = selectedCompressionFactory.getCompressionMethod();
|
final String compressionMethod = selectedCompressionFactory.getCompressionMethod();
|
||||||
connectionInternal.sendAndWaitForResponse(new Compress(compressionMethod), Compressed.class, Failure.class);
|
connectionInternal.sendAndWaitForResponse(new Compress(compressionMethod), Compressed.class, Failure.class);
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,9 @@ package org.jivesoftware.smack.fsm;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
|
||||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
|
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
|
||||||
import org.jivesoftware.smack.sasl.SASLErrorException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Note that this is an non-static inner class of XmppClientToServerConnection so that states can inspect and modify
|
* Note that this is an non-static inner class of XmppClientToServerConnection so that states can inspect and modify
|
||||||
|
@ -53,8 +51,7 @@ public abstract class State {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
public abstract StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
||||||
throws XMPPErrorException, SASLErrorException, IOException, SmackException,
|
throws IOException, SmackException, InterruptedException, XMPPException;
|
||||||
InterruptedException, FailedNonzaException;
|
|
||||||
|
|
||||||
public StateDescriptor getStateDescriptor() {
|
public StateDescriptor getStateDescriptor() {
|
||||||
return stateDescriptor;
|
return stateDescriptor;
|
||||||
|
|
|
@ -212,7 +212,7 @@ public abstract class StateDescriptor {
|
||||||
protected State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
protected State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
|
||||||
ModularXmppClientToServerConnection connection = connectionInternal.connection;
|
ModularXmppClientToServerConnection connection = connectionInternal.connection;
|
||||||
try {
|
try {
|
||||||
// If stateClassConstructor is null here, then you probably forgot to override the the
|
// If stateClassConstructor is null here, then you probably forgot to override the
|
||||||
// StateDescriptor.constructState() method?
|
// StateDescriptor.constructState() method?
|
||||||
return stateClassConstructor.newInstance(connection, this, connectionInternal);
|
return stateClassConstructor.newInstance(connection, this, connectionInternal);
|
||||||
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
|
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
|
||||||
|
|
|
@ -67,6 +67,14 @@ public abstract class StateTransitionResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class FailureCausedByTimeout extends Failure {
|
||||||
|
|
||||||
|
public FailureCausedByTimeout(String failureMessage) {
|
||||||
|
super(failureMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public abstract static class TransitionImpossible extends StateTransitionResult {
|
public abstract static class TransitionImpossible extends StateTransitionResult {
|
||||||
protected TransitionImpossible(String message) {
|
protected TransitionImpossible(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
|
@ -17,20 +17,16 @@
|
||||||
|
|
||||||
package org.jivesoftware.smack.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.util.EqualsUtil;
|
import org.jivesoftware.smack.util.EqualsUtil;
|
||||||
import org.jivesoftware.smack.util.HashCode;
|
import org.jivesoftware.smack.util.HashCode;
|
||||||
import org.jivesoftware.smack.util.Objects;
|
import org.jivesoftware.smack.util.Objects;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.jivesoftware.smack.util.TypedCloneable;
|
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
|
@ -62,7 +58,7 @@ import org.jxmpp.stringprep.XmppStringprepException;
|
||||||
* @author Matt Tucker
|
* @author Matt Tucker
|
||||||
*/
|
*/
|
||||||
public final class Message extends MessageOrPresence<MessageBuilder>
|
public final class Message extends MessageOrPresence<MessageBuilder>
|
||||||
implements MessageView, TypedCloneable<Message> {
|
implements MessageView {
|
||||||
|
|
||||||
public static final String ELEMENT = "message";
|
public static final String ELEMENT = "message";
|
||||||
public static final String BODY = "body";
|
public static final String BODY = "body";
|
||||||
|
@ -186,59 +182,6 @@ public final class Message extends MessageOrPresence<MessageBuilder>
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the default subject of the message, or null if the subject has not been set.
|
|
||||||
* The subject is a short description of message contents.
|
|
||||||
* <p>
|
|
||||||
* The default subject of a message is the subject that corresponds to the message's language.
|
|
||||||
* (see {@link #getLanguage()}) or if no language is set to the applications default
|
|
||||||
* language (see {@link Stanza#getDefaultLanguage()}).
|
|
||||||
*
|
|
||||||
* @return the subject of the message.
|
|
||||||
*/
|
|
||||||
public String getSubject() {
|
|
||||||
return getSubject(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the subject corresponding to the language. If the language is null, the method result
|
|
||||||
* will be the same as {@link #getSubject()}. Null will be returned if the language does not have
|
|
||||||
* a corresponding subject.
|
|
||||||
*
|
|
||||||
* @param language the language of the subject to return.
|
|
||||||
* @return the subject related to the passed in language.
|
|
||||||
*/
|
|
||||||
public String getSubject(String language) {
|
|
||||||
Subject subject = getMessageSubject(language);
|
|
||||||
return subject == null ? null : subject.subject;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Subject getMessageSubject(String language) {
|
|
||||||
language = determineLanguage(language);
|
|
||||||
for (Subject subject : getSubjects()) {
|
|
||||||
if (Objects.equals(language, subject.language)
|
|
||||||
|| (subject.language == null && Objects.equals(this.language, language))) {
|
|
||||||
return subject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a set of all subjects in this Message, including the default message subject accessible
|
|
||||||
* from {@link #getSubject()}.
|
|
||||||
*
|
|
||||||
* @return a collection of all subjects in this message.
|
|
||||||
*/
|
|
||||||
public Set<Subject> getSubjects() {
|
|
||||||
List<Subject> subjectList = getExtensions(Subject.class);
|
|
||||||
|
|
||||||
Set<Subject> subjects = new HashSet<>(subjectList.size());
|
|
||||||
subjects.addAll(subjectList);
|
|
||||||
|
|
||||||
return subjects;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the subject of the message. The subject is a short description of
|
* Sets the subject of the message. The subject is a short description of
|
||||||
* message contents.
|
* message contents.
|
||||||
|
@ -267,7 +210,7 @@ public final class Message extends MessageOrPresence<MessageBuilder>
|
||||||
@Deprecated
|
@Deprecated
|
||||||
// TODO: Remove when stanza builder is ready.
|
// TODO: Remove when stanza builder is ready.
|
||||||
public Subject addSubject(String language, String subject) {
|
public Subject addSubject(String language, String subject) {
|
||||||
language = determineLanguage(language);
|
language = Stanza.determineLanguage(this, language);
|
||||||
|
|
||||||
List<Subject> currentSubjects = getExtensions(Subject.class);
|
List<Subject> currentSubjects = getExtensions(Subject.class);
|
||||||
for (Subject currentSubject : currentSubjects) {
|
for (Subject currentSubject : currentSubjects) {
|
||||||
|
@ -290,7 +233,7 @@ public final class Message extends MessageOrPresence<MessageBuilder>
|
||||||
@Deprecated
|
@Deprecated
|
||||||
// TODO: Remove when stanza builder is ready.
|
// TODO: Remove when stanza builder is ready.
|
||||||
public boolean removeSubject(String language) {
|
public boolean removeSubject(String language) {
|
||||||
language = determineLanguage(language);
|
language = Stanza.determineLanguage(this, language);
|
||||||
for (Subject subject : getExtensions(Subject.class)) {
|
for (Subject subject : getExtensions(Subject.class)) {
|
||||||
if (language.equals(subject.language)) {
|
if (language.equals(subject.language)) {
|
||||||
return removeSubject(subject);
|
return removeSubject(subject);
|
||||||
|
@ -311,77 +254,6 @@ public final class Message extends MessageOrPresence<MessageBuilder>
|
||||||
return removeExtension(subject) != null;
|
return removeExtension(subject) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the languages being used for the subjects, not including the default subject.
|
|
||||||
*
|
|
||||||
* @return the languages being used for the subjects.
|
|
||||||
*/
|
|
||||||
public List<String> getSubjectLanguages() {
|
|
||||||
Subject defaultSubject = getMessageSubject(null);
|
|
||||||
List<String> languages = new ArrayList<String>();
|
|
||||||
for (Subject subject : getExtensions(Subject.class)) {
|
|
||||||
if (!subject.equals(defaultSubject)) {
|
|
||||||
languages.add(subject.language);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Collections.unmodifiableList(languages);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the default body of the message, or null if the body has not been set. The body
|
|
||||||
* is the main message contents.
|
|
||||||
* <p>
|
|
||||||
* The default body of a message is the body that corresponds to the message's language.
|
|
||||||
* (see {@link #getLanguage()}) or if no language is set to the applications default
|
|
||||||
* language (see {@link Stanza#getDefaultLanguage()}).
|
|
||||||
*
|
|
||||||
* @return the body of the message.
|
|
||||||
*/
|
|
||||||
public String getBody() {
|
|
||||||
return getBody(language);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the body corresponding to the language. If the language is null, the method result
|
|
||||||
* will be the same as {@link #getBody()}. Null will be returned if the language does not have
|
|
||||||
* a corresponding body.
|
|
||||||
*
|
|
||||||
* @param language the language of the body to return.
|
|
||||||
* @return the body related to the passed in language.
|
|
||||||
* @since 3.0.2
|
|
||||||
*/
|
|
||||||
public String getBody(String language) {
|
|
||||||
Body body = getMessageBody(language);
|
|
||||||
return body == null ? null : body.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Body getMessageBody(String language) {
|
|
||||||
language = determineLanguage(language);
|
|
||||||
for (Body body : getBodies()) {
|
|
||||||
if (Objects.equals(language, body.language) || (language != null && language.equals(this.language) && body.language == null)) {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a set of all bodies in this Message, including the default message body accessible
|
|
||||||
* from {@link #getBody()}.
|
|
||||||
*
|
|
||||||
* @return a collection of all bodies in this Message.
|
|
||||||
* @since 3.0.2
|
|
||||||
*/
|
|
||||||
public Set<Body> getBodies() {
|
|
||||||
List<ExtensionElement> bodiesList = getExtensions(Body.ELEMENT, Body.NAMESPACE);
|
|
||||||
Set<Body> resultSet = new HashSet<>(bodiesList.size());
|
|
||||||
for (ExtensionElement extensionElement : bodiesList) {
|
|
||||||
Body body = (Body) extensionElement;
|
|
||||||
resultSet.add(body);
|
|
||||||
}
|
|
||||||
return resultSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the body of the message.
|
* Sets the body of the message.
|
||||||
*
|
*
|
||||||
|
@ -431,7 +303,7 @@ public final class Message extends MessageOrPresence<MessageBuilder>
|
||||||
@Deprecated
|
@Deprecated
|
||||||
// TODO: Remove when stanza builder is ready.
|
// TODO: Remove when stanza builder is ready.
|
||||||
public Body addBody(String language, String body) {
|
public Body addBody(String language, String body) {
|
||||||
language = determineLanguage(language);
|
language = Stanza.determineLanguage(this, language);
|
||||||
|
|
||||||
removeBody(language);
|
removeBody(language);
|
||||||
|
|
||||||
|
@ -450,7 +322,7 @@ public final class Message extends MessageOrPresence<MessageBuilder>
|
||||||
@Deprecated
|
@Deprecated
|
||||||
// TODO: Remove when stanza builder is ready.
|
// TODO: Remove when stanza builder is ready.
|
||||||
public boolean removeBody(String language) {
|
public boolean removeBody(String language) {
|
||||||
language = determineLanguage(language);
|
language = Stanza.determineLanguage(this, language);
|
||||||
for (Body body : getBodies()) {
|
for (Body body : getBodies()) {
|
||||||
String bodyLanguage = body.getLanguage();
|
String bodyLanguage = body.getLanguage();
|
||||||
if (Objects.equals(bodyLanguage, language)) {
|
if (Objects.equals(bodyLanguage, language)) {
|
||||||
|
@ -476,37 +348,6 @@ public final class Message extends MessageOrPresence<MessageBuilder>
|
||||||
return removedElement != null;
|
return removedElement != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all the languages being used for the bodies, not including the default body.
|
|
||||||
*
|
|
||||||
* @return the languages being used for the bodies.
|
|
||||||
* @since 3.0.2
|
|
||||||
*/
|
|
||||||
public List<String> getBodyLanguages() {
|
|
||||||
Body defaultBody = getMessageBody(null);
|
|
||||||
List<String> languages = new ArrayList<String>();
|
|
||||||
for (Body body : getBodies()) {
|
|
||||||
if (!body.equals(defaultBody)) {
|
|
||||||
languages.add(body.language);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Collections.unmodifiableList(languages);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the thread id of the message, which is a unique identifier for a sequence
|
|
||||||
* of "chat" messages. If no thread id is set, <code>null</code> will be returned.
|
|
||||||
*
|
|
||||||
* @return the thread id of the message, or <code>null</code> if it doesn't exist.
|
|
||||||
*/
|
|
||||||
public String getThread() {
|
|
||||||
Message.Thread thread = getExtension(Message.Thread.class);
|
|
||||||
if (thread == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return thread.getThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the thread id of the message, which is a unique identifier for a sequence
|
* Sets the thread id of the message, which is a unique identifier for a sequence
|
||||||
* of "chat" messages.
|
* of "chat" messages.
|
||||||
|
@ -520,18 +361,6 @@ public final class Message extends MessageOrPresence<MessageBuilder>
|
||||||
addExtension(new Message.Thread(thread));
|
addExtension(new Message.Thread(thread));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String determineLanguage(String language) {
|
|
||||||
|
|
||||||
// empty string is passed by #setSubject() and #setBody() and is the same as null
|
|
||||||
language = "".equals(language) ? null : language;
|
|
||||||
|
|
||||||
// if given language is null check if message language is set
|
|
||||||
if (language == null && this.language != null) {
|
|
||||||
return this.language;
|
|
||||||
}
|
|
||||||
return language;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getElementName() {
|
public String getElementName() {
|
||||||
return ELEMENT;
|
return ELEMENT;
|
||||||
|
@ -542,6 +371,16 @@ public final class Message extends MessageOrPresence<MessageBuilder>
|
||||||
return StanzaBuilder.buildMessageFrom(this, getStanzaId());
|
return StanzaBuilder.buildMessageFrom(this, getStanzaId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBuilder asBuilder(String id) {
|
||||||
|
return StanzaBuilder.buildMessageFrom(this, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageBuilder asBuilder(XMPPConnection connection) {
|
||||||
|
return connection.getStanzaFactory().buildMessageStanzaFrom(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
@ -580,7 +419,10 @@ public final class Message extends MessageOrPresence<MessageBuilder>
|
||||||
* instance.
|
* instance.
|
||||||
* </p>
|
* </p>
|
||||||
* @return a clone of this message.
|
* @return a clone of this message.
|
||||||
|
* @deprecated use {@link #asBuilder()} instead.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Remove in Smack 4.5.
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public Message clone() {
|
public Message clone() {
|
||||||
return new Message(this);
|
return new Message(this);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2019 Florian Schmaus
|
* Copyright 2019-2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
|
||||||
public abstract class MessageOrPresence<MPB extends MessageOrPresenceBuilder<?, ?>> extends Stanza {
|
public abstract class MessageOrPresence<MPB extends MessageOrPresenceBuilder<?, ?>> extends Stanza {
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
@ -33,4 +35,8 @@ public abstract class MessageOrPresence<MPB extends MessageOrPresenceBuilder<?,
|
||||||
|
|
||||||
public abstract MPB asBuilder();
|
public abstract MPB asBuilder();
|
||||||
|
|
||||||
|
public abstract MPB asBuilder(String id);
|
||||||
|
|
||||||
|
public abstract MPB asBuilder(XMPPConnection connection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2019 Florian Schmaus
|
* Copyright 2003-2007 Jive Software, 2019-2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,15 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.Message.Subject;
|
||||||
|
import org.jivesoftware.smack.util.Objects;
|
||||||
|
|
||||||
public interface MessageView extends StanzaView {
|
public interface MessageView extends StanzaView {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,4 +35,158 @@ public interface MessageView extends StanzaView {
|
||||||
*/
|
*/
|
||||||
Message.Type getType();
|
Message.Type getType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default subject of the message, or null if the subject has not been set.
|
||||||
|
* The subject is a short description of message contents.
|
||||||
|
* <p>
|
||||||
|
* The default subject of a message is the subject that corresponds to the message's language.
|
||||||
|
* (see {@link #getLanguage()}) or if no language is set to the applications default
|
||||||
|
* language (see {@link Stanza#getDefaultLanguage()}).
|
||||||
|
*
|
||||||
|
* @return the subject of the message.
|
||||||
|
*/
|
||||||
|
default String getSubject() {
|
||||||
|
return getSubject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the subject corresponding to the language. If the language is null, the method result
|
||||||
|
* will be the same as {@link #getSubject()}. Null will be returned if the language does not have
|
||||||
|
* a corresponding subject.
|
||||||
|
*
|
||||||
|
* @param language the language of the subject to return.
|
||||||
|
* @return the subject related to the passed in language.
|
||||||
|
*/
|
||||||
|
default String getSubject(String language) {
|
||||||
|
Subject subject = getMessageSubject(language);
|
||||||
|
return subject == null ? null : subject.getSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
default Message.Subject getMessageSubject(String language) {
|
||||||
|
language = Stanza.determineLanguage(this, language);
|
||||||
|
for (Message.Subject subject : getSubjects()) {
|
||||||
|
if (Objects.equals(language, subject.getLanguage())
|
||||||
|
|| (subject.getLanguage() == null && Objects.equals(getLanguage(), language))) {
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a set of all subjects in this Message, including the default message subject accessible
|
||||||
|
* from {@link #getSubject()}.
|
||||||
|
*
|
||||||
|
* @return a collection of all subjects in this message.
|
||||||
|
*/
|
||||||
|
default Set<Message.Subject> getSubjects() {
|
||||||
|
List<Message.Subject> subjectList = getExtensions(Subject.class);
|
||||||
|
|
||||||
|
Set<Message.Subject> subjects = new HashSet<>(subjectList.size());
|
||||||
|
subjects.addAll(subjectList);
|
||||||
|
|
||||||
|
return subjects;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the languages being used for the subjects, not including the default subject.
|
||||||
|
*
|
||||||
|
* @return the languages being used for the subjects.
|
||||||
|
*/
|
||||||
|
default List<String> getSubjectLanguages() {
|
||||||
|
Message.Subject defaultSubject = getMessageSubject(null);
|
||||||
|
List<String> languages = new ArrayList<String>();
|
||||||
|
for (Message.Subject subject : getExtensions(Message.Subject.class)) {
|
||||||
|
if (!subject.equals(defaultSubject)) {
|
||||||
|
languages.add(subject.getLanguage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default body of the message, or null if the body has not been set. The body
|
||||||
|
* is the main message contents.
|
||||||
|
* <p>
|
||||||
|
* The default body of a message is the body that corresponds to the message's language.
|
||||||
|
* (see {@link #getLanguage()}) or if no language is set to the applications default
|
||||||
|
* language (see {@link Stanza#getDefaultLanguage()}).
|
||||||
|
*
|
||||||
|
* @return the body of the message.
|
||||||
|
*/
|
||||||
|
default String getBody() {
|
||||||
|
return getBody(getLanguage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the body corresponding to the language. If the language is null, the method result
|
||||||
|
* will be the same as {@link #getBody()}. Null will be returned if the language does not have
|
||||||
|
* a corresponding body.
|
||||||
|
*
|
||||||
|
* @param language the language of the body to return.
|
||||||
|
* @return the body related to the passed in language.
|
||||||
|
* @since 3.0.2
|
||||||
|
*/
|
||||||
|
default String getBody(String language) {
|
||||||
|
Message.Body body = getMessageBody(language);
|
||||||
|
return body == null ? null : body.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
default Message.Body getMessageBody(String language) {
|
||||||
|
language = Stanza.determineLanguage(this, language);
|
||||||
|
for (Message.Body body : getBodies()) {
|
||||||
|
if (Objects.equals(language, body.getLanguage()) || (language != null && language.equals(getLanguage()) && body.getLanguage() == null)) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a set of all bodies in this Message, including the default message body accessible
|
||||||
|
* from {@link #getBody()}.
|
||||||
|
*
|
||||||
|
* @return a collection of all bodies in this Message.
|
||||||
|
* @since 3.0.2
|
||||||
|
*/
|
||||||
|
default Set<Message.Body> getBodies() {
|
||||||
|
List<ExtensionElement> bodiesList = getExtensions(Message.Body.QNAME);
|
||||||
|
Set<Message.Body> resultSet = new HashSet<>(bodiesList.size());
|
||||||
|
for (ExtensionElement extensionElement : bodiesList) {
|
||||||
|
Message.Body body = (Message.Body) extensionElement;
|
||||||
|
resultSet.add(body);
|
||||||
|
}
|
||||||
|
return resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the languages being used for the bodies, not including the default body.
|
||||||
|
*
|
||||||
|
* @return the languages being used for the bodies.
|
||||||
|
* @since 3.0.2
|
||||||
|
*/
|
||||||
|
default List<String> getBodyLanguages() {
|
||||||
|
Message.Body defaultBody = getMessageBody(null);
|
||||||
|
List<String> languages = new ArrayList<String>();
|
||||||
|
for (Message.Body body : getBodies()) {
|
||||||
|
if (!body.equals(defaultBody)) {
|
||||||
|
languages.add(body.getLanguage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the thread id of the message, which is a unique identifier for a sequence
|
||||||
|
* of "chat" messages. If no thread id is set, <code>null</code> will be returned.
|
||||||
|
*
|
||||||
|
* @return the thread id of the message, or <code>null</code> if it doesn't exist.
|
||||||
|
*/
|
||||||
|
default String getThread() {
|
||||||
|
Message.Thread thread = getExtension(Message.Thread.class);
|
||||||
|
if (thread == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return thread.getThread();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,9 @@ package org.jivesoftware.smack.packet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.util.Objects;
|
import org.jivesoftware.smack.util.Objects;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.jivesoftware.smack.util.TypedCloneable;
|
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
|
@ -61,7 +61,7 @@ import org.jxmpp.jid.Jid;
|
||||||
* @author Matt Tucker
|
* @author Matt Tucker
|
||||||
*/
|
*/
|
||||||
public final class Presence extends MessageOrPresence<PresenceBuilder>
|
public final class Presence extends MessageOrPresence<PresenceBuilder>
|
||||||
implements PresenceView, TypedCloneable<Presence> {
|
implements PresenceView {
|
||||||
|
|
||||||
public static final String ELEMENT = "presence";
|
public static final String ELEMENT = "presence";
|
||||||
|
|
||||||
|
@ -282,6 +282,16 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
||||||
return StanzaBuilder.buildPresenceFrom(this, getStanzaId());
|
return StanzaBuilder.buildPresenceFrom(this, getStanzaId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PresenceBuilder asBuilder(String id) {
|
||||||
|
return StanzaBuilder.buildPresenceFrom(this, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PresenceBuilder asBuilder(XMPPConnection connection) {
|
||||||
|
return connection.getStanzaFactory().buildPresenceStanzaFrom(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
@ -343,7 +353,10 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
||||||
* instance.
|
* instance.
|
||||||
* </p>
|
* </p>
|
||||||
* @return a clone of this presence.
|
* @return a clone of this presence.
|
||||||
|
* @deprecated use {@link #asBuilder()} instead.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Remove in Smack 4.5.
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public Presence clone() {
|
public Presence clone() {
|
||||||
return new Presence(this);
|
return new Presence(this);
|
||||||
|
@ -354,7 +367,10 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
|
||||||
*
|
*
|
||||||
* @return a "clone" of this presence with a different stanza ID.
|
* @return a "clone" of this presence with a different stanza ID.
|
||||||
* @since 4.1.2
|
* @since 4.1.2
|
||||||
|
* @deprecated use {@link #asBuilder(XMPPConnection)} or {@link #asBuilder(String)}instead.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Remove in Smack 4.5.
|
||||||
|
@Deprecated
|
||||||
public Presence cloneWithNewId() {
|
public Presence cloneWithNewId() {
|
||||||
Presence clone = clone();
|
Presence clone = clone();
|
||||||
clone.setNewStanzaId();
|
clone.setNewStanzaId();
|
||||||
|
|
|
@ -575,4 +575,18 @@ public abstract class Stanza implements StanzaView, TopLevelStreamElement {
|
||||||
xml.append(error);
|
xml.append(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the provided non-empty language, or use this {@link XmlLangElement} language (if set).
|
||||||
|
*
|
||||||
|
* @param language the provided language, may be the empty string or <code>null</code>.
|
||||||
|
* @return the provided language or this element's language (if set).
|
||||||
|
*/
|
||||||
|
static String determineLanguage(XmlLangElement xmlLangElement, String language) {
|
||||||
|
if (language != null && !language.isEmpty()) {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
|
return xmlLangElement.getLanguage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2018 Florian Schmaus
|
* Copyright 2018-2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,12 +16,15 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
public final class TlsFailure implements Nonza {
|
public final class TlsFailure implements Nonza {
|
||||||
|
|
||||||
public static final TlsFailure INSTANCE = new TlsFailure();
|
public static final TlsFailure INSTANCE = new TlsFailure();
|
||||||
|
|
||||||
public static final String ELEMENT = "failure";
|
public static final String ELEMENT = "failure";
|
||||||
public static final String NAMESPACE = TlsProceed.NAMESPACE;
|
public static final String NAMESPACE = TlsProceed.NAMESPACE;
|
||||||
|
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
|
||||||
|
|
||||||
private TlsFailure() {
|
private TlsFailure() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2018 Florian Schmaus
|
* Copyright 2018-2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,12 +16,15 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
public final class TlsProceed implements Nonza {
|
public final class TlsProceed implements Nonza {
|
||||||
|
|
||||||
public static final TlsProceed INSTANCE = new TlsProceed();
|
public static final TlsProceed INSTANCE = new TlsProceed();
|
||||||
|
|
||||||
public static final String ELEMENT = "proceed";
|
public static final String ELEMENT = "proceed";
|
||||||
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls";
|
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls";
|
||||||
|
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
|
||||||
|
|
||||||
private TlsProceed() {
|
private TlsProceed() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright © 2015-2019 Florian Schmaus
|
* Copyright © 2015-2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -34,7 +34,7 @@ import java.util.Set;
|
||||||
* @param <K> the type of the keys the map uses.
|
* @param <K> the type of the keys the map uses.
|
||||||
* @param <V> the type of the values the map uses.
|
* @param <V> the type of the values the map uses.
|
||||||
*/
|
*/
|
||||||
public class MultiMap<K, V> implements TypedCloneable<MultiMap<K, V>> {
|
public class MultiMap<K, V> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The constant value {@value}.
|
* The constant value {@value}.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015 Florian Schmaus
|
* Copyright 2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,19 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.util;
|
package org.jivesoftware.smack.util;
|
||||||
|
|
||||||
/**
|
// TODO: Replace with java.util.function.Supplier once Smack's minimum Android SDK level is 24 or higher.
|
||||||
* An extended version of {@link java.lang.Cloneable}, which defines a generic {@link #clone()}
|
public interface Supplier<T> {
|
||||||
* method.
|
|
||||||
*
|
|
||||||
* @param <T> the type returned by {@link #clone()}.
|
|
||||||
*/
|
|
||||||
public interface TypedCloneable<T> extends Cloneable {
|
|
||||||
|
|
||||||
/**
|
T get();
|
||||||
* Clone this instance.
|
|
||||||
*
|
|
||||||
* @return a cloned version of this instance.
|
|
||||||
*/
|
|
||||||
T clone();
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.compress.packet;
|
package org.jivesoftware.smack.compress.packet;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.packet;
|
package org.jivesoftware.smack.packet;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.provider;
|
package org.jivesoftware.smack.provider;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smack.util;
|
package org.jivesoftware.smack.util;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlNotSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlNotSimilar;
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
|
@ -18,7 +18,7 @@ package org.jivesoftware.smack.util;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.StandardExtensionElement;
|
import org.jivesoftware.smack.packet.StandardExtensionElement;
|
||||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
import org.jivesoftware.smack.test.util.XmlUnitUtils;
|
import org.jivesoftware.smack.test.util.XmlAssertUtil;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -38,10 +38,10 @@ public class XmlStringBuilderTest {
|
||||||
String expectedXml = "<outer xmlns='outer-namespace'><inner xmlns='inner-namespace'></inner><inner xmlns='inner-namespace'></inner></outer>";
|
String expectedXml = "<outer xmlns='outer-namespace'><inner xmlns='inner-namespace'></inner><inner xmlns='inner-namespace'></inner></outer>";
|
||||||
XmlStringBuilder actualXml = outer.toXML(XmlEnvironment.EMPTY);
|
XmlStringBuilder actualXml = outer.toXML(XmlEnvironment.EMPTY);
|
||||||
|
|
||||||
XmlUnitUtils.assertXmlSimilar(expectedXml, actualXml);
|
XmlAssertUtil.assertXmlSimilar(expectedXml, actualXml);
|
||||||
|
|
||||||
StringBuilder actualXmlTwo = actualXml.toXML(XmlEnvironment.EMPTY);
|
StringBuilder actualXmlTwo = actualXml.toXML(XmlEnvironment.EMPTY);
|
||||||
|
|
||||||
XmlUnitUtils.assertXmlSimilar(expectedXml, actualXmlTwo);
|
XmlAssertUtil.assertXmlSimilar(expectedXml, actualXmlTwo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2014-2019 Florian Schmaus
|
* Copyright 2014-2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -26,8 +26,7 @@ import org.xmlunit.diff.DefaultNodeMatcher;
|
||||||
import org.xmlunit.diff.ElementSelectors;
|
import org.xmlunit.diff.ElementSelectors;
|
||||||
import org.xmlunit.input.NormalizedSource;
|
import org.xmlunit.input.NormalizedSource;
|
||||||
|
|
||||||
// TODO: Rename this class to XmlAssertUtil
|
public class XmlAssertUtil {
|
||||||
public class XmlUnitUtils {
|
|
||||||
|
|
||||||
public static void assertXmlNotSimilar(CharSequence xmlOne, CharSequence xmlTwo) {
|
public static void assertXmlNotSimilar(CharSequence xmlOne, CharSequence xmlTwo) {
|
||||||
normalizedCompare(xmlOne, xmlTwo).areNotSimilar();
|
normalizedCompare(xmlOne, xmlTwo).areNotSimilar();
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.httpfileupload;
|
package org.jivesoftware.smackx.httpfileupload;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.httpfileupload;
|
package org.jivesoftware.smackx.httpfileupload;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.httpfileupload.provider;
|
package org.jivesoftware.smackx.httpfileupload.provider;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.mam;
|
package org.jivesoftware.smackx.mam;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.StreamOpen;
|
import org.jivesoftware.smack.packet.StreamOpen;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.message_fastening;
|
package org.jivesoftware.smackx.message_fastening;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.message_markup;
|
package org.jivesoftware.smackx.message_markup;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.message_retraction.element;
|
package org.jivesoftware.smackx.message_retraction.element;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.message_retraction.element;
|
package org.jivesoftware.smackx.message_retraction.element;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.reference;
|
package org.jivesoftware.smackx.reference;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.sid;
|
package org.jivesoftware.smackx.sid;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.spoiler;
|
package org.jivesoftware.smackx.spoiler;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
|
@ -962,8 +962,9 @@ public final class ServiceDiscoveryManager extends Manager {
|
||||||
// to respect ConnectionConfiguration.isSendPresence()
|
// to respect ConnectionConfiguration.isSendPresence()
|
||||||
final Presence presenceSend = this.presenceSend;
|
final Presence presenceSend = this.presenceSend;
|
||||||
if (connection.isAuthenticated() && presenceSend != null) {
|
if (connection.isAuthenticated() && presenceSend != null) {
|
||||||
|
Presence presence = presenceSend.asBuilder(connection).build();
|
||||||
try {
|
try {
|
||||||
connection.sendStanza(presenceSend.cloneWithNewId());
|
connection.sendStanza(presence);
|
||||||
}
|
}
|
||||||
catch (InterruptedException | NotConnectedException e) {
|
catch (InterruptedException | NotConnectedException e) {
|
||||||
LOGGER.log(Level.WARNING, "Could could not update presence with caps info", e);
|
LOGGER.log(Level.WARNING, "Could could not update presence with caps info", e);
|
||||||
|
|
|
@ -30,7 +30,6 @@ import org.jivesoftware.smack.packet.IqData;
|
||||||
import org.jivesoftware.smack.util.EqualsUtil;
|
import org.jivesoftware.smack.util.EqualsUtil;
|
||||||
import org.jivesoftware.smack.util.HashCode;
|
import org.jivesoftware.smack.util.HashCode;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.jivesoftware.smack.util.TypedCloneable;
|
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
import org.jxmpp.util.XmppStringUtils;
|
import org.jxmpp.util.XmppStringUtils;
|
||||||
|
@ -44,7 +43,7 @@ import org.jxmpp.util.XmppStringUtils;
|
||||||
*
|
*
|
||||||
* @author Gaston Dombiak
|
* @author Gaston Dombiak
|
||||||
*/
|
*/
|
||||||
public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable<DiscoverInfo> {
|
public class DiscoverInfo extends IQ implements DiscoverInfoView {
|
||||||
|
|
||||||
public static final String ELEMENT = QUERY_ELEMENT;
|
public static final String ELEMENT = QUERY_ELEMENT;
|
||||||
public static final String NAMESPACE = "http://jabber.org/protocol/disco#info";
|
public static final String NAMESPACE = "http://jabber.org/protocol/disco#info";
|
||||||
|
@ -303,7 +302,13 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable
|
||||||
return new DiscoverInfoBuilder(this, stanzaId);
|
return new DiscoverInfoBuilder(this, stanzaId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Deprecate in favor of asBuilder().
|
/**
|
||||||
|
* Deprecated, do not use.
|
||||||
|
*
|
||||||
|
* @deprecated use {@link #asBuilder(String)} instead.
|
||||||
|
*/
|
||||||
|
// TODO: Remove in Smack 4.5.
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public DiscoverInfo clone() {
|
public DiscoverInfo clone() {
|
||||||
return new DiscoverInfo(this);
|
return new DiscoverInfo(this);
|
||||||
|
@ -516,7 +521,7 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable
|
||||||
* as well as specific feature types of interest, if any (e.g., for the purpose of feature
|
* as well as specific feature types of interest, if any (e.g., for the purpose of feature
|
||||||
* negotiation).
|
* negotiation).
|
||||||
*/
|
*/
|
||||||
public static final class Feature implements TypedCloneable<Feature> {
|
public static final class Feature {
|
||||||
|
|
||||||
private final String variable;
|
private final String variable;
|
||||||
|
|
||||||
|
@ -566,11 +571,6 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable
|
||||||
return variable.hashCode();
|
return variable.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Feature clone() {
|
|
||||||
return new Feature(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return toXML().toString();
|
return toXML().toString();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2015-2016 Florian Schmaus
|
* Copyright 2015-2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -60,15 +60,17 @@ public final class MucEnterConfiguration {
|
||||||
since = builder.since;
|
since = builder.since;
|
||||||
timeout = builder.timeout;
|
timeout = builder.timeout;
|
||||||
|
|
||||||
|
final PresenceBuilder joinPresenceBuilder;
|
||||||
if (builder.joinPresence == null) {
|
if (builder.joinPresence == null) {
|
||||||
joinPresence = builder.joinPresenceBuilder.ofType(Presence.Type.available).build();
|
joinPresenceBuilder = builder.joinPresenceBuilder.ofType(Presence.Type.available);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
joinPresence = builder.joinPresence.clone();
|
joinPresenceBuilder = builder.joinPresence.asBuilder();
|
||||||
}
|
}
|
||||||
// Indicate the the client supports MUC
|
// Indicate the the client supports MUC
|
||||||
joinPresence.addExtension(new MUCInitialPresence(password, maxChars, maxStanzas, seconds,
|
joinPresenceBuilder.addExtension(new MUCInitialPresence(password, maxChars, maxStanzas, seconds,
|
||||||
since));
|
since));
|
||||||
|
joinPresence = joinPresenceBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
Presence getJoinPresence(MultiUserChat multiUserChat) {
|
Presence getJoinPresence(MultiUserChat multiUserChat) {
|
||||||
|
@ -92,6 +94,8 @@ public final class MucEnterConfiguration {
|
||||||
private long timeout;
|
private long timeout;
|
||||||
|
|
||||||
private final PresenceBuilder joinPresenceBuilder;
|
private final PresenceBuilder joinPresenceBuilder;
|
||||||
|
|
||||||
|
// TODO: Remove in Smack 4.5.
|
||||||
private Presence joinPresence;
|
private Presence joinPresence;
|
||||||
|
|
||||||
Builder(Resourcepart nickname, XMPPConnection connection) {
|
Builder(Resourcepart nickname, XMPPConnection connection) {
|
||||||
|
|
|
@ -88,7 +88,6 @@ import org.jxmpp.jid.EntityJid;
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
import org.jxmpp.jid.parts.Resourcepart;
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
import org.jxmpp.util.cache.ExpirationCache;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A MultiUserChat room (XEP-45), created with {@link MultiUserChatManager#getMultiUserChat(EntityBareJid)}.
|
* A MultiUserChat room (XEP-45), created with {@link MultiUserChatManager#getMultiUserChat(EntityBareJid)}.
|
||||||
|
@ -112,9 +111,6 @@ import org.jxmpp.util.cache.ExpirationCache;
|
||||||
public class MultiUserChat {
|
public class MultiUserChat {
|
||||||
private static final Logger LOGGER = Logger.getLogger(MultiUserChat.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(MultiUserChat.class.getName());
|
||||||
|
|
||||||
private static final ExpirationCache<DomainBareJid, Void> KNOWN_MUC_SERVICES = new ExpirationCache<>(
|
|
||||||
100, 1000 * 60 * 60 * 24);
|
|
||||||
|
|
||||||
private final XMPPConnection connection;
|
private final XMPPConnection connection;
|
||||||
private final EntityBareJid room;
|
private final EntityBareJid room;
|
||||||
private final MultiUserChatManager multiUserChatManager;
|
private final MultiUserChatManager multiUserChatManager;
|
||||||
|
@ -340,13 +336,9 @@ public class MultiUserChat {
|
||||||
private Presence enter(MucEnterConfiguration conf) throws NotConnectedException, NoResponseException,
|
private Presence enter(MucEnterConfiguration conf) throws NotConnectedException, NoResponseException,
|
||||||
XMPPErrorException, InterruptedException, NotAMucServiceException {
|
XMPPErrorException, InterruptedException, NotAMucServiceException {
|
||||||
final DomainBareJid mucService = room.asDomainBareJid();
|
final DomainBareJid mucService = room.asDomainBareJid();
|
||||||
if (!KNOWN_MUC_SERVICES.containsKey(mucService)) {
|
if (!multiUserChatManager.providesMucService(mucService)) {
|
||||||
if (multiUserChatManager.providesMucService(mucService)) {
|
|
||||||
KNOWN_MUC_SERVICES.put(mucService, null);
|
|
||||||
} else {
|
|
||||||
throw new NotAMucServiceException(this);
|
throw new NotAMucServiceException(this);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// We enter a room by sending a presence packet where the "to"
|
// We enter a room by sending a presence packet where the "to"
|
||||||
// field is in the form "roomName@service/nickname"
|
// field is in the form "roomName@service/nickname"
|
||||||
Presence joinPresence = conf.getJoinPresence(this);
|
Presence joinPresence = conf.getJoinPresence(this);
|
||||||
|
|
|
@ -64,6 +64,7 @@ import org.jxmpp.jid.EntityFullJid;
|
||||||
import org.jxmpp.jid.EntityJid;
|
import org.jxmpp.jid.EntityJid;
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
import org.jxmpp.jid.parts.Resourcepart;
|
import org.jxmpp.jid.parts.Resourcepart;
|
||||||
|
import org.jxmpp.util.cache.ExpirationCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A manager for Multi-User Chat rooms.
|
* A manager for Multi-User Chat rooms.
|
||||||
|
@ -136,6 +137,9 @@ public final class MultiUserChatManager extends Manager {
|
||||||
private static final StanzaFilter INVITATION_FILTER = new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(new MUCUser()),
|
private static final StanzaFilter INVITATION_FILTER = new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(new MUCUser()),
|
||||||
new NotFilter(MessageTypeFilter.ERROR));
|
new NotFilter(MessageTypeFilter.ERROR));
|
||||||
|
|
||||||
|
private static final ExpirationCache<DomainBareJid, Void> KNOWN_MUC_SERVICES = new ExpirationCache<>(
|
||||||
|
100, 1000 * 60 * 60 * 24);
|
||||||
|
|
||||||
private final Set<InvitationListener> invitationsListeners = new CopyOnWriteArraySet<InvitationListener>();
|
private final Set<InvitationListener> invitationsListeners = new CopyOnWriteArraySet<InvitationListener>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -392,8 +396,16 @@ public final class MultiUserChatManager extends Manager {
|
||||||
*/
|
*/
|
||||||
public boolean providesMucService(DomainBareJid domainBareJid) throws NoResponseException,
|
public boolean providesMucService(DomainBareJid domainBareJid) throws NoResponseException,
|
||||||
XMPPErrorException, NotConnectedException, InterruptedException {
|
XMPPErrorException, NotConnectedException, InterruptedException {
|
||||||
return serviceDiscoveryManager.supportsFeature(domainBareJid,
|
boolean contains = KNOWN_MUC_SERVICES.containsKey(domainBareJid);
|
||||||
MUCInitialPresence.NAMESPACE);
|
if (!contains) {
|
||||||
|
if (serviceDiscoveryManager.supportsFeature(domainBareJid,
|
||||||
|
MUCInitialPresence.NAMESPACE)) {
|
||||||
|
KNOWN_MUC_SERVICES.put(domainBareJid, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contains;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,7 +32,6 @@ import org.jivesoftware.smackx.pubsub.Affiliation.AffiliationNamespace;
|
||||||
*/
|
*/
|
||||||
public class AffiliationsExtension extends NodeExtension {
|
public class AffiliationsExtension extends NodeExtension {
|
||||||
protected List<Affiliation> items = Collections.emptyList();
|
protected List<Affiliation> items = Collections.emptyList();
|
||||||
private final String node;
|
|
||||||
|
|
||||||
public AffiliationsExtension() {
|
public AffiliationsExtension() {
|
||||||
this(null);
|
this(null);
|
||||||
|
@ -51,9 +50,8 @@ public class AffiliationsExtension extends NodeExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AffiliationsExtension(AffiliationNamespace affiliationsNamespace, List<Affiliation> subList, String node) {
|
public AffiliationsExtension(AffiliationNamespace affiliationsNamespace, List<Affiliation> subList, String node) {
|
||||||
super(affiliationsNamespace.type);
|
super(affiliationsNamespace.type, node);
|
||||||
items = subList;
|
items = subList;
|
||||||
this.node = node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Affiliation> getAffiliations() {
|
public List<Affiliation> getAffiliations() {
|
||||||
|
@ -61,19 +59,14 @@ public class AffiliationsExtension extends NodeExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
if ((items == null) || (items.size() == 0)) {
|
if ((items == null) || (items.size() == 0)) {
|
||||||
return super.toXML(enclosingNamespace);
|
xml.closeEmptyElement();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
// Can't use XmlStringBuilder(this), because we don't want the namespace to be included
|
|
||||||
XmlStringBuilder xml = new XmlStringBuilder();
|
|
||||||
xml.halfOpenElement(getElementName());
|
|
||||||
xml.optAttribute("node", node);
|
|
||||||
xml.rightAngleBracket();
|
xml.rightAngleBracket();
|
||||||
xml.append(items);
|
xml.append(items);
|
||||||
xml.closeElement(this);
|
xml.closeElement(this);
|
||||||
return xml;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.pubsub;
|
package org.jivesoftware.smackx.pubsub;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,26 +71,14 @@ public class FormNode extends NodeExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
if (configForm == null) {
|
if (configForm == null) {
|
||||||
return super.toXML(enclosingNamespace);
|
xml.closeEmptyElement();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
StringBuilder builder = new StringBuilder("<");
|
|
||||||
builder.append(getElementName());
|
|
||||||
|
|
||||||
if (getNode() != null) {
|
xml.append(configForm);
|
||||||
builder.append(" node='");
|
xml.closeElement(this);
|
||||||
builder.append(getNode());
|
|
||||||
builder.append("'>");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
builder.append('>');
|
|
||||||
builder.append(configForm.toXML());
|
|
||||||
builder.append("</");
|
|
||||||
builder.append(getElementName() + '>');
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,13 +54,9 @@ public class GetItemsRequest extends NodeExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
XmlStringBuilder xml = new XmlStringBuilder();
|
|
||||||
xml.halfOpenElement(getElementName());
|
|
||||||
xml.attribute("node", getNode());
|
|
||||||
xml.optAttribute("subid", getSubscriptionId());
|
xml.optAttribute("subid", getSubscriptionId());
|
||||||
xml.optIntAttribute("max_items", getMaxItems());
|
xml.optIntAttribute("max_items", getMaxItems());
|
||||||
xml.closeEmptyElement();
|
xml.closeEmptyElement();
|
||||||
return xml;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,21 +150,9 @@ public class Item extends NodeExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
XmlStringBuilder xml = getCommonXml();
|
|
||||||
|
|
||||||
xml.closeEmptyElement();
|
|
||||||
|
|
||||||
return xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final XmlStringBuilder getCommonXml() {
|
|
||||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
|
||||||
|
|
||||||
xml.optAttribute("id", getId());
|
xml.optAttribute("id", getId());
|
||||||
xml.optAttribute("node", getNode());
|
xml.closeEmptyElement();
|
||||||
|
|
||||||
return xml;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,6 +20,7 @@ import java.util.List;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
import org.jivesoftware.smack.packet.NamedElement;
|
import org.jivesoftware.smack.packet.NamedElement;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used for multiple purposes.
|
* This class is used for multiple purposes.
|
||||||
|
@ -150,35 +151,21 @@ public class ItemsExtension extends NodeExtension implements EmbeddedPacketExten
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
if ((items == null) || (items.size() == 0)) {
|
if ((items == null) || (items.size() == 0)) {
|
||||||
return super.toXML(enclosingNamespace);
|
xml.closeEmptyElement();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
StringBuilder builder = new StringBuilder("<");
|
|
||||||
builder.append(getElementName());
|
|
||||||
builder.append(" node='");
|
|
||||||
builder.append(getNode());
|
|
||||||
|
|
||||||
if (notify != null) {
|
if (notify != null) {
|
||||||
builder.append("' ");
|
xml.attribute(type.getElementAttribute(), notify);
|
||||||
builder.append(type.getElementAttribute());
|
xml.rightAngleBracket();
|
||||||
builder.append("='");
|
} else {
|
||||||
builder.append(notify.equals(Boolean.TRUE) ? 1 : 0);
|
xml.rightAngleBracket();
|
||||||
builder.append("'>");
|
xml.append(items);
|
||||||
}
|
|
||||||
else {
|
|
||||||
builder.append("'>");
|
|
||||||
for (NamedElement item : items) {
|
|
||||||
builder.append(item.toXML());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.append("</");
|
xml.closeElement(this);
|
||||||
builder.append(getElementName());
|
|
||||||
builder.append('>');
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package org.jivesoftware.smackx.pubsub;
|
package org.jivesoftware.smackx.pubsub;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
|
import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
|
||||||
|
|
||||||
|
@ -78,8 +80,17 @@ public class NodeExtension implements ExtensionElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
public final XmlStringBuilder toXML(XmlEnvironment enclosingNamespace) {
|
||||||
return '<' + getElementName() + (node == null ? "" : " node='" + node + '\'') + "/>";
|
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
|
||||||
|
xml.optAttribute("node", node);
|
||||||
|
|
||||||
|
addXml(xml);
|
||||||
|
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
|
xml.closeEmptyElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -50,13 +50,15 @@ public class OptionsExtension extends NodeExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
XmlStringBuilder xml = new XmlStringBuilder();
|
xml.rightAngleBracket();
|
||||||
|
|
||||||
xml.halfOpenElement(getElementName());
|
xml.halfOpenElement(getElementName());
|
||||||
xml.attribute("jid", jid);
|
xml.attribute("jid", jid);
|
||||||
xml.optAttribute("node", getNode());
|
xml.optAttribute("node", getNode());
|
||||||
xml.optAttribute("subid", id);
|
xml.optAttribute("subid", id);
|
||||||
|
|
||||||
xml.closeEmptyElement();
|
xml.closeEmptyElement();
|
||||||
return xml;
|
xml.closeElement(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,14 +132,11 @@ public class PayloadItem<E extends ExtensionElement> extends Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
XmlStringBuilder xml = getCommonXml();
|
xml.optAttribute("id", getId());
|
||||||
|
|
||||||
xml.rightAngleBracket();
|
xml.rightAngleBracket();
|
||||||
xml.append(payload.toXML());
|
xml.append(payload);
|
||||||
xml.closeElement(this);
|
xml.closeElement(this);
|
||||||
|
|
||||||
return xml;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.jivesoftware.smackx.pubsub;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a request to publish an item(s) to a specific node.
|
* Represents a request to publish an item(s) to a specific node.
|
||||||
*
|
*
|
||||||
|
@ -51,18 +53,9 @@ public class PublishItem<T extends Item> extends NodeExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
StringBuilder builder = new StringBuilder("<");
|
xml.rightAngleBracket();
|
||||||
builder.append(getElementName());
|
xml.append(items);
|
||||||
builder.append(" node='");
|
xml.closeElement(this);
|
||||||
builder.append(getNode());
|
|
||||||
builder.append("'>");
|
|
||||||
|
|
||||||
for (Item item : items) {
|
|
||||||
builder.append(item.toXML());
|
|
||||||
}
|
|
||||||
builder.append("</publish>");
|
|
||||||
|
|
||||||
return builder.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.pubsub;
|
package org.jivesoftware.smackx.pubsub;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
|
||||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
|
@ -44,11 +43,8 @@ public class SubscribeExtension extends NodeExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
|
|
||||||
xml.optAttribute("node", getNode());
|
|
||||||
xml.attribute("jid", getJid());
|
xml.attribute("jid", getJid());
|
||||||
xml.closeEmptyElement();
|
xml.closeEmptyElement();
|
||||||
return xml;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,16 +138,11 @@ public class Subscription extends NodeExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
XmlStringBuilder builder = new XmlStringBuilder(this);
|
xml.attribute("jid", jid);
|
||||||
builder.attribute("jid", jid);
|
xml.optAttribute("subid", id);
|
||||||
|
xml.optAttribute("subscription", state);
|
||||||
builder.optAttribute("node", getNode());
|
xml.closeEmptyElement();
|
||||||
builder.optAttribute("subid", id);
|
|
||||||
builder.optAttribute("subscription", state.toString());
|
|
||||||
|
|
||||||
builder.closeEmptyElement();
|
|
||||||
return builder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.jivesoftware.smackx.pubsub;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the element holding the list of subscription elements.
|
* Represents the element holding the list of subscription elements.
|
||||||
*
|
*
|
||||||
|
@ -91,29 +93,13 @@ public class SubscriptionsExtension extends NodeExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
if ((items == null) || (items.size() == 0)) {
|
if ((items == null) || (items.size() == 0)) {
|
||||||
return super.toXML(enclosingNamespace);
|
xml.closeEmptyElement();
|
||||||
}
|
return;
|
||||||
else {
|
|
||||||
StringBuilder builder = new StringBuilder("<");
|
|
||||||
builder.append(getElementName());
|
|
||||||
|
|
||||||
if (getNode() != null) {
|
|
||||||
builder.append(" node='");
|
|
||||||
builder.append(getNode());
|
|
||||||
builder.append('\'');
|
|
||||||
}
|
|
||||||
builder.append('>');
|
|
||||||
|
|
||||||
for (Subscription item : items) {
|
|
||||||
builder.append(item.toXML());
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.append("</");
|
|
||||||
builder.append(getElementName());
|
|
||||||
builder.append('>');
|
|
||||||
return builder.toString();
|
|
||||||
}
|
}
|
||||||
|
xml.rightAngleBracket();
|
||||||
|
xml.append(items);
|
||||||
|
xml.closeElement(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,13 +51,9 @@ public class UnsubscribeExtension extends NodeExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
|
protected void addXml(XmlStringBuilder xml) {
|
||||||
XmlStringBuilder xml = new XmlStringBuilder();
|
|
||||||
xml.halfOpenElement(getElementName());
|
|
||||||
xml.attribute("jid", jid);
|
xml.attribute("jid", jid);
|
||||||
xml.optAttribute("node", getNode());
|
|
||||||
xml.optAttribute("subid", id);
|
xml.optAttribute("subid", id);
|
||||||
xml.closeEmptyElement();
|
xml.closeEmptyElement();
|
||||||
return xml;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.attention;
|
package org.jivesoftware.smackx.attention;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.attention.packet.AttentionExtension;
|
import org.jivesoftware.smackx.attention.packet.AttentionExtension;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.bytestreams.ibb.packet;
|
package org.jivesoftware.smackx.bytestreams.ibb.packet;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.bytestreams.ibb.packet;
|
package org.jivesoftware.smackx.bytestreams.ibb.packet;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.bytestreams.ibb.packet;
|
package org.jivesoftware.smackx.bytestreams.ibb.packet;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.bytestreams.ibb.packet;
|
package org.jivesoftware.smackx.bytestreams.ibb.packet;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.last_interaction;
|
package org.jivesoftware.smackx.last_interaction;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.mood;
|
package org.jivesoftware.smackx.mood;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.mood;
|
package org.jivesoftware.smackx.mood;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.nick;
|
package org.jivesoftware.smackx.nick;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2017 Florian Schmaus
|
* Copyright 2017-2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,13 +16,14 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.pubsub;
|
package org.jivesoftware.smackx.pubsub;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.pubsub.Affiliation.Type;
|
import org.jivesoftware.smackx.pubsub.Affiliation.Type;
|
||||||
|
import org.jivesoftware.smackx.pubsub.packet.PubSub;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.jxmpp.jid.BareJid;
|
import org.jxmpp.jid.BareJid;
|
||||||
|
@ -40,10 +41,10 @@ public class AffiliationsExtensionTest {
|
||||||
|
|
||||||
AffiliationsExtension affiliationsExtension = new AffiliationsExtension(affiliationsList, "testNode");
|
AffiliationsExtension affiliationsExtension = new AffiliationsExtension(affiliationsList, "testNode");
|
||||||
|
|
||||||
CharSequence xml = affiliationsExtension.toXML();
|
CharSequence xml = affiliationsExtension.toXML(PubSub.NAMESPACE);
|
||||||
|
|
||||||
assertXmlSimilar("<affiliations node='testNode'><affiliation xmlns='http://jabber.org/protocol/pubsub#owner' jid='one@exampleone.org' affiliation='member'/></affiliations>",
|
assertXmlSimilar("<affiliations node='testNode'><affiliation xmlns='http://jabber.org/protocol/pubsub#owner' jid='one@exampleone.org' affiliation='member'/></affiliations>",
|
||||||
xml.toString());
|
xml);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.pubsub;
|
package org.jivesoftware.smackx.pubsub;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.softwareinfo;
|
package org.jivesoftware.smackx.softwareinfo;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.usertune;
|
package org.jivesoftware.smackx.usertune;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jivesoftware.smackx.xdata;
|
package org.jivesoftware.smackx.xdata;
|
||||||
|
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.jxmpp.jid.JidTestUtil;
|
import org.jxmpp.jid.JidTestUtil;
|
||||||
|
|
|
@ -1047,7 +1047,7 @@ public final class Roster extends Manager {
|
||||||
}
|
}
|
||||||
if (presence == null) {
|
if (presence == null) {
|
||||||
if (unavailable != null) {
|
if (unavailable != null) {
|
||||||
return unavailable.clone();
|
return unavailable;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
presence = synthesizeUnvailablePresence(jid);
|
presence = synthesizeUnvailablePresence(jid);
|
||||||
|
@ -1055,7 +1055,7 @@ public final class Roster extends Manager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return presence.clone();
|
return presence;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1084,7 +1084,7 @@ public final class Roster extends Manager {
|
||||||
return presence;
|
return presence;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return presence.clone();
|
return presence;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1107,7 +1107,7 @@ public final class Roster extends Manager {
|
||||||
} else {
|
} else {
|
||||||
res = new ArrayList<>(userPresences.values().size());
|
res = new ArrayList<>(userPresences.values().size());
|
||||||
for (Presence presence : userPresences.values()) {
|
for (Presence presence : userPresences.values()) {
|
||||||
res.add(presence.clone());
|
res.add(presence);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
@ -1156,7 +1156,7 @@ public final class Roster extends Manager {
|
||||||
Presence unavailable = null;
|
Presence unavailable = null;
|
||||||
for (Presence presence : userPresences.values()) {
|
for (Presence presence : userPresences.values()) {
|
||||||
if (presence.isAvailable()) {
|
if (presence.isAvailable()) {
|
||||||
answer.add(presence.clone());
|
answer.add(presence);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
unavailable = presence;
|
unavailable = presence;
|
||||||
|
@ -1166,7 +1166,7 @@ public final class Roster extends Manager {
|
||||||
res = answer;
|
res = answer;
|
||||||
}
|
}
|
||||||
else if (unavailable != null) {
|
else if (unavailable != null) {
|
||||||
res = Arrays.asList(unavailable.clone());
|
res = Arrays.asList(unavailable);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Presence presence = synthesizeUnvailablePresence(jid);
|
Presence presence = synthesizeUnvailablePresence(jid);
|
||||||
|
|
|
@ -454,10 +454,12 @@ public class SmackIntegrationTestFramework {
|
||||||
}
|
}
|
||||||
sb.append('\n');
|
sb.append('\n');
|
||||||
}
|
}
|
||||||
sb.append("Available tests: ").append(numberOfAvailableTests)
|
sb.append("Available tests: ").append(numberOfAvailableTests);
|
||||||
.append("(#-classes: ").append(testRunResult.disabledTestClasses.size())
|
if (!testRunResult.disabledTestClasses.isEmpty() || !testRunResult.disabledTests.isEmpty()) {
|
||||||
.append(", #-tests: ").append(testRunResult.disabledTests.size())
|
sb.append(" (Disabled ").append(testRunResult.disabledTestClasses.size()).append(" classes")
|
||||||
.append(")\n");
|
.append(" and ").append(testRunResult.disabledTests.size()).append(" tests");
|
||||||
|
}
|
||||||
|
sb.append('\n');
|
||||||
LOGGER.info(sb.toString());
|
LOGGER.info(sb.toString());
|
||||||
|
|
||||||
for (PreparedTest test : tests) {
|
for (PreparedTest test : tests) {
|
||||||
|
@ -863,6 +865,8 @@ public class SmackIntegrationTestFramework {
|
||||||
}
|
}
|
||||||
testMethod.invoke(test, connectionsArray);
|
testMethod.invoke(test, connectionsArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connectionManager.recycle(connections);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -480,6 +480,8 @@ public class XmppConnectionManager {
|
||||||
synchronized (connectionPool) {
|
synchronized (connectionPool) {
|
||||||
connectionPool.put(connectionClass, connection);
|
connectionPool.put(connectionClass, connection);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
connection.disconnect();
|
||||||
}
|
}
|
||||||
// Note that we do not delete the account of the unauthenticated connection here, as it is done at the end of
|
// Note that we do not delete the account of the unauthenticated connection here, as it is done at the end of
|
||||||
// the test run together with all other dynamically created accounts.
|
// the test run together with all other dynamically created accounts.
|
||||||
|
|
|
@ -58,11 +58,15 @@ public class LoginIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
|
||||||
AbstractXMPPConnection connection = getUnconnectedConnection();
|
AbstractXMPPConnection connection = getUnconnectedConnection();
|
||||||
connection.connect();
|
connection.connect();
|
||||||
|
|
||||||
|
try {
|
||||||
SASLErrorException saslErrorException = assertThrows(SASLErrorException.class,
|
SASLErrorException saslErrorException = assertThrows(SASLErrorException.class,
|
||||||
() -> connection.login(nonExistentUserString, invalidPassword));
|
() -> connection.login(nonExistentUserString, invalidPassword));
|
||||||
|
|
||||||
SaslNonza.SASLFailure saslFailure = saslErrorException.getSASLFailure();
|
SaslNonza.SASLFailure saslFailure = saslErrorException.getSASLFailure();
|
||||||
assertEquals(SASLError.not_authorized, saslFailure.getSASLError());
|
assertEquals(SASLError.not_authorized, saslFailure.getSASLError());
|
||||||
|
} finally {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,12 +38,7 @@ public class WaitForClosingStreamElementTest extends AbstractSmackLowLevelIntegr
|
||||||
|
|
||||||
Field closingStreamReceivedField = AbstractXMPPConnection.class.getDeclaredField("closingStreamReceived");
|
Field closingStreamReceivedField = AbstractXMPPConnection.class.getDeclaredField("closingStreamReceived");
|
||||||
closingStreamReceivedField.setAccessible(true);
|
closingStreamReceivedField.setAccessible(true);
|
||||||
SynchronizationPoint<?> closingStreamReceived = (SynchronizationPoint<?>) closingStreamReceivedField.get(connection);
|
boolean closingStreamReceived = (boolean) closingStreamReceivedField.get(connection);
|
||||||
Exception failureException = closingStreamReceived.getFailureException();
|
assertTrue(closingStreamReceived);
|
||||||
if (failureException != null) {
|
|
||||||
throw new AssertionError("Sync poing yielded failure exception", failureException);
|
|
||||||
}
|
|
||||||
boolean closingStreamReceivedSuccessful = closingStreamReceived.wasSuccessful();
|
|
||||||
assertTrue(closingStreamReceivedSuccessful);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import org.jivesoftware.smack.MessageListener;
|
import org.jivesoftware.smack.MessageListener;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
|
@ -39,6 +40,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
|
||||||
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
import org.igniterealtime.smack.inttest.TestNotPossibleException;
|
||||||
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
|
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
|
||||||
import org.igniterealtime.smack.inttest.util.ResultSyncPoint;
|
import org.igniterealtime.smack.inttest.util.ResultSyncPoint;
|
||||||
|
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
|
||||||
import org.jxmpp.jid.DomainBareJid;
|
import org.jxmpp.jid.DomainBareJid;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
@ -120,4 +122,41 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
|
||||||
mucAsSeenByOne.leave();
|
mucAsSeenByOne.leave();
|
||||||
mucAsSeenByTwo.leave();
|
mucAsSeenByTwo.leave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SmackIntegrationTest
|
||||||
|
public void mucDestroyTest() throws TimeoutException, Exception {
|
||||||
|
|
||||||
|
EntityBareJid mucAddress = JidCreate.entityBareFrom(Localpart.from("smack-inttest-join-leave-" + randomString),
|
||||||
|
mucService.getDomain());
|
||||||
|
|
||||||
|
MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress);
|
||||||
|
muc.join(Resourcepart.from("nick-one"));
|
||||||
|
|
||||||
|
final SimpleResultSyncPoint mucDestroyed = new SimpleResultSyncPoint();
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
DefaultUserStatusListener userStatusListener = new DefaultUserStatusListener() {
|
||||||
|
@Override
|
||||||
|
public void roomDestroyed(MultiUserChat alternateMUC, String reason) {
|
||||||
|
mucDestroyed.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
muc.addUserStatusListener(userStatusListener);
|
||||||
|
|
||||||
|
assertTrue(mucManagerOne.getJoinedRooms().size() == 1);
|
||||||
|
assertTrue(muc.getOccupantsCount() == 1);
|
||||||
|
assertTrue(muc.getNickname() != null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
muc.destroy("Dummy reason", null);
|
||||||
|
mucDestroyed.waitForResult(timeout);
|
||||||
|
} finally {
|
||||||
|
muc.removeUserStatusListener(userStatusListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(mucManagerOne.getJoinedRooms().size() == 0);
|
||||||
|
assertTrue(muc.getOccupantsCount() == 0);
|
||||||
|
assertTrue(muc.getNickname() == null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package org.jivesoftware.smackx.ox;
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertEquals;
|
import static junit.framework.TestCase.assertEquals;
|
||||||
import static junit.framework.TestCase.assertNotNull;
|
import static junit.framework.TestCase.assertNotNull;
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
package org.jivesoftware.smackx.ox;
|
package org.jivesoftware.smackx.ox;
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertEquals;
|
import static junit.framework.TestCase.assertEquals;
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
package org.jivesoftware.smackx.ox;
|
package org.jivesoftware.smackx.ox;
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertEquals;
|
import static junit.framework.TestCase.assertEquals;
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
package org.jivesoftware.smackx.ox;
|
package org.jivesoftware.smackx.ox;
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertTrue;
|
import static junit.framework.TestCase.assertTrue;
|
||||||
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
|
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
|
@ -56,7 +56,6 @@ import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
import org.jxmpp.jid.JidTestUtil;
|
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
|
|
||||||
public class OXInstantMessagingManagerTest extends SmackTestSuite {
|
public class OXInstantMessagingManagerTest extends SmackTestSuite {
|
||||||
|
@ -71,16 +70,10 @@ public class OXInstantMessagingManagerTest extends SmackTestSuite {
|
||||||
public void test() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
|
public void test() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
|
||||||
NoSuchProviderException, SmackException, MissingUserIdOnKeyException, InterruptedException, XMPPException,
|
NoSuchProviderException, SmackException, MissingUserIdOnKeyException, InterruptedException, XMPPException,
|
||||||
XmlPullParserException {
|
XmlPullParserException {
|
||||||
DummyConnection aliceCon = new DummyConnection(
|
DummyConnection aliceCon = new DummyConnection();
|
||||||
DummyConnection.DummyConnectionConfiguration.builder()
|
|
||||||
.setXmppDomain(JidTestUtil.EXAMPLE_ORG)
|
|
||||||
.setUsernameAndPassword("alice", "dummypass").build());
|
|
||||||
aliceCon.connect().login();
|
aliceCon.connect().login();
|
||||||
|
|
||||||
DummyConnection bobCon = new DummyConnection(
|
DummyConnection bobCon = new DummyConnection();
|
||||||
DummyConnection.DummyConnectionConfiguration.builder()
|
|
||||||
.setXmppDomain(JidTestUtil.EXAMPLE_ORG)
|
|
||||||
.setUsernameAndPassword("bob", "dummypass").build());
|
|
||||||
bobCon.connect().login();
|
bobCon.connect().login();
|
||||||
|
|
||||||
FileBasedOpenPgpStore aliceStore = new FileBasedOpenPgpStore(new File(basePath, "alice"));
|
FileBasedOpenPgpStore aliceStore = new FileBasedOpenPgpStore(new File(basePath, "alice"));
|
||||||
|
|
|
@ -26,10 +26,9 @@ import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException.ConnectionException;
|
|
||||||
import org.jivesoftware.smack.SmackException.EndpointConnectionException;
|
import org.jivesoftware.smack.SmackException.EndpointConnectionException;
|
||||||
import org.jivesoftware.smack.SynchronizationPoint;
|
|
||||||
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
|
||||||
|
import org.jivesoftware.smack.fsm.StateTransitionResult;
|
||||||
import org.jivesoftware.smack.tcp.XmppTcpTransportModule.EstablishingTcpConnectionState;
|
import org.jivesoftware.smack.tcp.XmppTcpTransportModule.EstablishingTcpConnectionState;
|
||||||
import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
|
import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
|
||||||
import org.jivesoftware.smack.util.Async;
|
import org.jivesoftware.smack.util.Async;
|
||||||
|
@ -48,7 +47,10 @@ public final class ConnectionAttemptState {
|
||||||
final SocketChannel socketChannel;
|
final SocketChannel socketChannel;
|
||||||
|
|
||||||
final List<RemoteConnectionException<?>> connectionExceptions;
|
final List<RemoteConnectionException<?>> connectionExceptions;
|
||||||
final SynchronizationPoint<ConnectionException> tcpConnectionEstablishedSyncPoint;
|
|
||||||
|
EndpointConnectionException connectionException;
|
||||||
|
boolean connected;
|
||||||
|
long deadline;
|
||||||
|
|
||||||
final Iterator<Rfc6120TcpRemoteConnectionEndpoint> connectionEndpointIterator;
|
final Iterator<Rfc6120TcpRemoteConnectionEndpoint> connectionEndpointIterator;
|
||||||
/** The current connection endpoint we are trying */
|
/** The current connection endpoint we are trying */
|
||||||
|
@ -65,17 +67,32 @@ public final class ConnectionAttemptState {
|
||||||
socketChannel = SocketChannel.open();
|
socketChannel = SocketChannel.open();
|
||||||
socketChannel.configureBlocking(false);
|
socketChannel.configureBlocking(false);
|
||||||
|
|
||||||
connectionEndpointIterator = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.iterator();
|
List<Rfc6120TcpRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints;
|
||||||
|
connectionEndpointIterator = endpoints.iterator();
|
||||||
connectionEndpoint = connectionEndpointIterator.next();
|
connectionEndpoint = connectionEndpointIterator.next();
|
||||||
connectionExceptions = new ArrayList<>(discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.size());
|
connectionExceptions = new ArrayList<>(endpoints.size());
|
||||||
|
|
||||||
tcpConnectionEstablishedSyncPoint = new SynchronizationPoint<>(connectionInternal.connection,
|
|
||||||
"TCP connection establishment");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void establishTcpConnection() {
|
StateTransitionResult.Failure establishTcpConnection() throws InterruptedException {
|
||||||
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address = nextAddress();
|
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address = nextAddress();
|
||||||
establishTcpConnection(address);
|
establishTcpConnection(address);
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
while (!connected && connectionException == null) {
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
if (now >= deadline) {
|
||||||
|
return new StateTransitionResult.FailureCausedByTimeout("Timeout waiting to establish connection");
|
||||||
|
}
|
||||||
|
wait (deadline - now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (connected) {
|
||||||
|
assert connectionException == null;
|
||||||
|
// Success case: we have been able to establish a connection to one remote endpoint.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StateTransitionResult.FailureCausedByException<Exception>(connectionException);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void establishTcpConnection(
|
private void establishTcpConnection(
|
||||||
|
@ -84,8 +101,10 @@ public final class ConnectionAttemptState {
|
||||||
establishingTcpConnectionState, address);
|
establishingTcpConnectionState, address);
|
||||||
connectionInternal.invokeConnectionStateMachineListener(connectingToHostEvent);
|
connectionInternal.invokeConnectionStateMachineListener(connectingToHostEvent);
|
||||||
|
|
||||||
final boolean connected;
|
|
||||||
final InetSocketAddress inetSocketAddress = address.getInetSocketAddress();
|
final InetSocketAddress inetSocketAddress = address.getInetSocketAddress();
|
||||||
|
// TODO: Should use "connect timeout" instead of reply timeout. But first connect timeout needs to be moved from
|
||||||
|
// XMPPTCPConnectionConfiguration. into XMPPConnectionConfiguration.
|
||||||
|
deadline = System.currentTimeMillis() + connectionInternal.connection.getReplyTimeout();
|
||||||
try {
|
try {
|
||||||
connected = socketChannel.connect(inetSocketAddress);
|
connected = socketChannel.connect(inetSocketAddress);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -98,7 +117,9 @@ public final class ConnectionAttemptState {
|
||||||
establishingTcpConnectionState, address, true);
|
establishingTcpConnectionState, address, true);
|
||||||
connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
|
connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
|
||||||
|
|
||||||
tcpConnectionEstablishedSyncPoint.reportSuccess();
|
synchronized (this) {
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +145,10 @@ public final class ConnectionAttemptState {
|
||||||
establishingTcpConnectionState, address, false);
|
establishingTcpConnectionState, address, false);
|
||||||
connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
|
connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
|
||||||
|
|
||||||
// Do not set 'state' here, since this is processed by a reactor thread, which doesn't hold
|
connected = true;
|
||||||
// the objects lock.
|
synchronized (ConnectionAttemptState.this) {
|
||||||
tcpConnectionEstablishedSyncPoint.reportSuccess();
|
notifyAll();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (ClosedChannelException e) {
|
} catch (ClosedChannelException e) {
|
||||||
onIOExceptionWhenEstablishingTcpConnection(e, address);
|
onIOExceptionWhenEstablishingTcpConnection(e, address);
|
||||||
|
@ -137,14 +159,14 @@ public final class ConnectionAttemptState {
|
||||||
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> failedAddress) {
|
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> failedAddress) {
|
||||||
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> nextInetSocketAddress = nextAddress();
|
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> nextInetSocketAddress = nextAddress();
|
||||||
if (nextInetSocketAddress == null) {
|
if (nextInetSocketAddress == null) {
|
||||||
EndpointConnectionException connectionException = EndpointConnectionException.from(
|
connectionException = EndpointConnectionException.from(
|
||||||
discoveredEndpoints.result.lookupFailures, connectionExceptions);
|
discoveredEndpoints.result.lookupFailures, connectionExceptions);
|
||||||
tcpConnectionEstablishedSyncPoint.reportFailure(connectionException);
|
synchronized (this) {
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tcpConnectionEstablishedSyncPoint.resetTimeout();
|
|
||||||
|
|
||||||
RemoteConnectionException<Rfc6120TcpRemoteConnectionEndpoint> rce = new RemoteConnectionException<>(
|
RemoteConnectionException<Rfc6120TcpRemoteConnectionEndpoint> rce = new RemoteConnectionException<>(
|
||||||
failedAddress, exception);
|
failedAddress, exception);
|
||||||
connectionExceptions.add(rce);
|
connectionExceptions.add(rce);
|
||||||
|
|
|
@ -26,11 +26,6 @@ import java.io.Writer;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
import java.security.UnrecoverableKeyException;
|
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -44,7 +39,6 @@ import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.Semaphore;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -65,13 +59,12 @@ import org.jivesoftware.smack.SmackException.AlreadyConnectedException;
|
||||||
import org.jivesoftware.smack.SmackException.AlreadyLoggedInException;
|
import org.jivesoftware.smack.SmackException.AlreadyLoggedInException;
|
||||||
import org.jivesoftware.smack.SmackException.ConnectionException;
|
import org.jivesoftware.smack.SmackException.ConnectionException;
|
||||||
import org.jivesoftware.smack.SmackException.EndpointConnectionException;
|
import org.jivesoftware.smack.SmackException.EndpointConnectionException;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||||
|
import org.jivesoftware.smack.SmackException.SecurityNotPossibleException;
|
||||||
import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
|
import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
|
||||||
import org.jivesoftware.smack.SmackFuture;
|
import org.jivesoftware.smack.SmackFuture;
|
||||||
import org.jivesoftware.smack.StanzaListener;
|
import org.jivesoftware.smack.StanzaListener;
|
||||||
import org.jivesoftware.smack.SynchronizationPoint;
|
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
||||||
|
@ -151,8 +144,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
|
|
||||||
private SSLSocket secureSocket;
|
private SSLSocket secureSocket;
|
||||||
|
|
||||||
private final Semaphore readerWriterSemaphore = new Semaphore(2);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protected access level because of unit test purposes
|
* Protected access level because of unit test purposes
|
||||||
*/
|
*/
|
||||||
|
@ -166,14 +157,12 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private final SynchronizationPoint<XMPPException> maybeCompressFeaturesReceived = new SynchronizationPoint<XMPPException>(
|
private boolean streamFeaturesAfterAuthenticationReceived;
|
||||||
this, "stream compression feature");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private final SynchronizationPoint<SmackException> compressSyncPoint = new SynchronizationPoint<>(
|
private boolean compressSyncPoint;
|
||||||
this, "stream compression");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default bundle and defer callback, used for new connections.
|
* The default bundle and defer callback, used for new connections.
|
||||||
|
@ -197,15 +186,26 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
/**
|
/**
|
||||||
* The stream ID of the stream that is currently resumable, ie. the stream we hold the state
|
* The stream ID of the stream that is currently resumable, ie. the stream we hold the state
|
||||||
* for in {@link #clientHandledStanzasCount}, {@link #serverHandledStanzasCount} and
|
* for in {@link #clientHandledStanzasCount}, {@link #serverHandledStanzasCount} and
|
||||||
* {@link #unacknowledgedStanzas}.
|
* {@link #unFailedNonzaExceptionacknowledgedStanzas}.
|
||||||
*/
|
*/
|
||||||
private String smSessionId;
|
private String smSessionId;
|
||||||
|
|
||||||
private final SynchronizationPoint<FailedNonzaException> smResumedSyncPoint = new SynchronizationPoint<>(
|
/**
|
||||||
this, "stream resumed element");
|
* Represents the state of stream management resumption.
|
||||||
|
* <p>
|
||||||
|
* Unlike other sync points, this sync point is marked volatile because it is also read by the reader thread.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private volatile SyncPointState smResumedSyncPoint;
|
||||||
|
private Failed smResumptionFailed;
|
||||||
|
|
||||||
private final SynchronizationPoint<SmackException> smEnabledSyncPoint = new SynchronizationPoint<>(
|
/**
|
||||||
this, "stream enabled element");
|
* Represents the state of stream magement.
|
||||||
|
* <p>
|
||||||
|
* This boolean is marked volatile as it is read by various threads, including the reader thread via {@link #isSmEnabled()}.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private volatile boolean smEnabledSyncPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The client's preferred maximum resumption time in seconds.
|
* The client's preferred maximum resumption time in seconds.
|
||||||
|
@ -376,20 +376,26 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
SmackException, IOException, InterruptedException {
|
SmackException, IOException, InterruptedException {
|
||||||
// Authenticate using SASL
|
// Authenticate using SASL
|
||||||
SSLSession sslSession = secureSocket != null ? secureSocket.getSession() : null;
|
SSLSession sslSession = secureSocket != null ? secureSocket.getSession() : null;
|
||||||
|
|
||||||
|
streamFeaturesAfterAuthenticationReceived = false;
|
||||||
authenticate(username, password, config.getAuthzid(), sslSession);
|
authenticate(username, password, config.getAuthzid(), sslSession);
|
||||||
|
|
||||||
// Wait for stream features after the authentication.
|
// Wait for stream features after the authentication.
|
||||||
// TODO: The name of this synchronization point "maybeCompressFeaturesReceived" is not perfect. It should be
|
// TODO: The name of this synchronization point "maybeCompressFeaturesReceived" is not perfect. It should be
|
||||||
// renamed to "streamFeaturesAfterAuthenticationReceived".
|
// renamed to "streamFeaturesAfterAuthenticationReceived".
|
||||||
maybeCompressFeaturesReceived.checkIfSuccessOrWait();
|
waitForConditionOrThrowConnectionException(() -> streamFeaturesAfterAuthenticationReceived, "compress features from server");
|
||||||
|
|
||||||
// If compression is enabled then request the server to use stream compression. XEP-170
|
// If compression is enabled then request the server to use stream compression. XEP-170
|
||||||
// recommends to perform stream compression before resource binding.
|
// recommends to perform stream compression before resource binding.
|
||||||
maybeEnableCompression();
|
maybeEnableCompression();
|
||||||
|
|
||||||
|
smResumedSyncPoint = SyncPointState.initial;
|
||||||
|
smResumptionFailed = null;
|
||||||
if (isSmResumptionPossible()) {
|
if (isSmResumptionPossible()) {
|
||||||
smResumedSyncPoint.sendAndWaitForResponse(new Resume(clientHandledStanzasCount, smSessionId));
|
smResumedSyncPoint = SyncPointState.request_sent;
|
||||||
if (smResumedSyncPoint.wasSuccessful()) {
|
sendNonza(new Resume(clientHandledStanzasCount, smSessionId));
|
||||||
|
waitForCondition(() -> smResumedSyncPoint == SyncPointState.successful || smResumptionFailed != null, "resume previous stream");
|
||||||
|
if (smResumedSyncPoint == SyncPointState.successful) {
|
||||||
// We successfully resumed the stream, be done here
|
// We successfully resumed the stream, be done here
|
||||||
afterSuccessfulLogin(true);
|
afterSuccessfulLogin(true);
|
||||||
return;
|
return;
|
||||||
|
@ -397,7 +403,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
// SM resumption failed, what Smack does here is to report success of
|
// SM resumption failed, what Smack does here is to report success of
|
||||||
// lastFeaturesReceived in case of sm resumption was answered with 'failed' so that
|
// lastFeaturesReceived in case of sm resumption was answered with 'failed' so that
|
||||||
// normal resource binding can be tried.
|
// normal resource binding can be tried.
|
||||||
LOGGER.fine("Stream resumption failed, continuing with normal stream establishment process");
|
assert smResumptionFailed != null;
|
||||||
|
LOGGER.fine("Stream resumption failed, continuing with normal stream establishment process: " + smResumptionFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Stanza> previouslyUnackedStanzas = new LinkedList<Stanza>();
|
List<Stanza> previouslyUnackedStanzas = new LinkedList<Stanza>();
|
||||||
|
@ -418,12 +425,14 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
// unacknowledgedStanzas and become duplicated on reconnect. See SMACK-706.
|
// unacknowledgedStanzas and become duplicated on reconnect. See SMACK-706.
|
||||||
bindResourceAndEstablishSession(resource);
|
bindResourceAndEstablishSession(resource);
|
||||||
|
|
||||||
|
smEnabledSyncPoint = false;
|
||||||
if (isSmAvailable() && useSm) {
|
if (isSmAvailable() && useSm) {
|
||||||
// Remove what is maybe left from previously stream managed sessions
|
// Remove what is maybe left from previously stream managed sessions
|
||||||
serverHandledStanzasCount = 0;
|
serverHandledStanzasCount = 0;
|
||||||
|
sendNonza(new Enable(useSmResumption, smClientMaxResumptionTime));
|
||||||
// XEP-198 3. Enabling Stream Management. If the server response to 'Enable' is 'Failed'
|
// XEP-198 3. Enabling Stream Management. If the server response to 'Enable' is 'Failed'
|
||||||
// then this is a non recoverable error and we therefore throw an exception.
|
// then this is a non recoverable error and we therefore throw an exception.
|
||||||
smEnabledSyncPoint.sendAndWaitForResponseOrThrow(new Enable(useSmResumption, smClientMaxResumptionTime));
|
waitForConditionOrThrowConnectionException(() -> smEnabledSyncPoint, "enabling stream mangement");
|
||||||
synchronized (requestAckPredicates) {
|
synchronized (requestAckPredicates) {
|
||||||
if (requestAckPredicates.isEmpty()) {
|
if (requestAckPredicates.isEmpty()) {
|
||||||
// Assure that we have at lest one predicate set up that so that we request acks
|
// Assure that we have at lest one predicate set up that so that we request acks
|
||||||
|
@ -485,6 +494,10 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutdown(boolean instant) {
|
private void shutdown(boolean instant) {
|
||||||
|
// The writer thread may already been finished at this point, for example when the connection is in the
|
||||||
|
// disconnected-but-resumable state. There is no need to wait for the closing stream tag from the server in this
|
||||||
|
// case.
|
||||||
|
if (!packetWriter.done()) {
|
||||||
// First shutdown the writer, this will result in a closing stream element getting send to
|
// First shutdown the writer, this will result in a closing stream element getting send to
|
||||||
// the server
|
// the server
|
||||||
LOGGER.finer("PacketWriter shutdown()");
|
LOGGER.finer("PacketWriter shutdown()");
|
||||||
|
@ -494,6 +507,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
if (!instant) {
|
if (!instant) {
|
||||||
waitForClosingStreamTagFromServer();
|
waitForClosingStreamTagFromServer();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LOGGER.finer("PacketReader shutdown()");
|
LOGGER.finer("PacketReader shutdown()");
|
||||||
packetReader.shutdown();
|
packetReader.shutdown();
|
||||||
|
@ -503,9 +517,14 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
|
|
||||||
setWasAuthenticated();
|
setWasAuthenticated();
|
||||||
|
|
||||||
// Wait for reader and writer threads to be terminated.
|
try {
|
||||||
readerWriterSemaphore.acquireUninterruptibly(2);
|
boolean readerAndWriterThreadsTermianted = waitForCondition(() -> !packetWriter.running && !packetReader.running);
|
||||||
readerWriterSemaphore.release(2);
|
if (!readerAndWriterThreadsTermianted) {
|
||||||
|
LOGGER.severe("Reader and writer threads did not terminate");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOGGER.log(Level.FINE, "Interrupted while waiting for reader and writer threads to terminate", e);
|
||||||
|
}
|
||||||
|
|
||||||
if (disconnectedButResumeable) {
|
if (disconnectedButResumeable) {
|
||||||
return;
|
return;
|
||||||
|
@ -533,15 +552,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
initState();
|
initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void initState() {
|
|
||||||
super.initState();
|
|
||||||
maybeCompressFeaturesReceived.init();
|
|
||||||
compressSyncPoint.init();
|
|
||||||
smResumedSyncPoint.init();
|
|
||||||
smEnabledSyncPoint.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendNonza(Nonza element) throws NotConnectedException, InterruptedException {
|
public void sendNonza(Nonza element) throws NotConnectedException, InterruptedException {
|
||||||
packetWriter.sendStreamElement(element);
|
packetWriter.sendStreamElement(element);
|
||||||
|
@ -652,15 +662,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
// Set the reader and writer instance variables
|
// Set the reader and writer instance variables
|
||||||
initReaderAndWriter();
|
initReaderAndWriter();
|
||||||
|
|
||||||
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
|
// Start the writer thread. This will open an XMPP stream to the server
|
||||||
packetWriter.init();
|
packetWriter.init();
|
||||||
// Start the reader thread. The startup() method will block until we
|
// Start the reader thread. The startup() method will block until we
|
||||||
|
@ -688,17 +689,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
* existing plain connection and perform a handshake. This method won't return until the
|
* existing plain connection and perform a handshake. This method won't return until the
|
||||||
* connection has finished the handshake or an error occurred while securing the connection.
|
* connection has finished the handshake or an error occurred while securing the connection.
|
||||||
* @throws IOException if an I/O error occurred.
|
* @throws IOException if an I/O error occurred.
|
||||||
* @throws CertificateException
|
* @throws SecurityNotPossibleException if TLS is not possible.
|
||||||
* @throws NoSuchAlgorithmException if no such algorithm is available.
|
* @throws CertificateException if there is an issue with the certificate.
|
||||||
* @throws NoSuchProviderException
|
|
||||||
* @throws KeyStoreException
|
|
||||||
* @throws UnrecoverableKeyException
|
|
||||||
* @throws KeyManagementException if there was a key mangement error.
|
|
||||||
* @throws SmackException if Smack detected an exceptional situation.
|
|
||||||
* @throws Exception if an exception occurs.
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("LiteralClassName")
|
@SuppressWarnings("LiteralClassName")
|
||||||
private void proceedTLSReceived() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, NoSuchProviderException, UnrecoverableKeyException, KeyManagementException, SmackException {
|
private void proceedTLSReceived() throws IOException, SecurityNotPossibleException, CertificateException {
|
||||||
SmackTlsContext smackTlsContext = getSmackTlsContext();
|
SmackTlsContext smackTlsContext = getSmackTlsContext();
|
||||||
|
|
||||||
Socket plain = socket;
|
Socket plain = socket;
|
||||||
|
@ -773,7 +768,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUsingCompression() {
|
public boolean isUsingCompression() {
|
||||||
return compressionHandler != null && compressSyncPoint.wasSuccessful();
|
return compressionHandler != null && compressSyncPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -791,10 +786,10 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
*
|
*
|
||||||
* @throws NotConnectedException if the XMPP connection is not connected.
|
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||||
* @throws SmackException if Smack detected an exceptional situation.
|
* @throws SmackException if Smack detected an exceptional situation.
|
||||||
* @throws NoResponseException if there was no response from the remote entity.
|
|
||||||
* @throws InterruptedException if the calling thread was interrupted.
|
* @throws InterruptedException if the calling thread was interrupted.
|
||||||
|
* @throws XMPPException if an XMPP protocol error was received.
|
||||||
*/
|
*/
|
||||||
private void maybeEnableCompression() throws SmackException, InterruptedException {
|
private void maybeEnableCompression() throws SmackException, InterruptedException, XMPPException {
|
||||||
if (!config.isCompressionEnabled()) {
|
if (!config.isCompressionEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -807,7 +802,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
// If stream compression was offered by the server and we want to use
|
// If stream compression was offered by the server and we want to use
|
||||||
// compression then send compression request to the server
|
// compression then send compression request to the server
|
||||||
if ((compressionHandler = maybeGetCompressionHandler(compression)) != null) {
|
if ((compressionHandler = maybeGetCompressionHandler(compression)) != null) {
|
||||||
compressSyncPoint.sendAndWaitForResponseOrThrow(new Compress(compressionHandler.getCompressionMethod()));
|
compressSyncPoint = false;
|
||||||
|
sendNonza(new Compress(compressionHandler.getCompressionMethod()));
|
||||||
|
waitForConditionOrThrowConnectionException(() -> compressSyncPoint, "establishing stream compression");
|
||||||
} else {
|
} else {
|
||||||
LOGGER.warning("Could not enable compression because no matching handler/method pair was found");
|
LOGGER.warning("Could not enable compression because no matching handler/method pair was found");
|
||||||
}
|
}
|
||||||
|
@ -835,11 +832,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
// We connected successfully to the servers TCP port
|
// We connected successfully to the servers TCP port
|
||||||
initConnection();
|
initConnection();
|
||||||
|
|
||||||
// TLS handled will be successful either if TLS was established, or if it was not mandatory.
|
// TLS handled will be true either if TLS was established, or if it was not mandatory.
|
||||||
tlsHandled.checkIfSuccessOrWaitOrThrow();
|
waitForConditionOrThrowConnectionException(() -> tlsHandled, "establishing TLS");
|
||||||
|
|
||||||
// Wait with SASL auth until the SASL mechanisms have been received
|
// Wait with SASL auth until the SASL mechanisms have been received
|
||||||
saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
|
waitForConditionOrThrowConnectionException(() -> saslFeatureReceived, "SASL mechanisms stream feature from server");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -857,24 +854,28 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
if (startTlsFeature != null) {
|
if (startTlsFeature != null) {
|
||||||
if (startTlsFeature.required() && config.getSecurityMode() == SecurityMode.disabled) {
|
if (startTlsFeature.required() && config.getSecurityMode() == SecurityMode.disabled) {
|
||||||
SecurityRequiredByServerException smackException = new SecurityRequiredByServerException();
|
SecurityRequiredByServerException smackException = new SecurityRequiredByServerException();
|
||||||
tlsHandled.reportFailure(smackException);
|
currentSmackException = smackException;
|
||||||
|
notifyWaitingThreads();
|
||||||
throw smackException;
|
throw smackException;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.getSecurityMode() != ConnectionConfiguration.SecurityMode.disabled) {
|
if (config.getSecurityMode() != ConnectionConfiguration.SecurityMode.disabled) {
|
||||||
sendNonza(new StartTls());
|
sendNonza(new StartTls());
|
||||||
} else {
|
} else {
|
||||||
tlsHandled.reportSuccess();
|
tlsHandled = true;
|
||||||
|
notifyWaitingThreads();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tlsHandled.reportSuccess();
|
tlsHandled = true;
|
||||||
|
notifyWaitingThreads();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSaslAuthenticated()) {
|
if (isSaslAuthenticated()) {
|
||||||
// If we have received features after the SASL has been successfully completed, then we
|
// If we have received features after the SASL has been successfully completed, then we
|
||||||
// have also *maybe* received, as it is an optional feature, the compression feature
|
// have also *maybe* received, as it is an optional feature, the compression feature
|
||||||
// from the server.
|
// from the server.
|
||||||
maybeCompressFeaturesReceived.reportSuccess();
|
streamFeaturesAfterAuthenticationReceived = true;
|
||||||
|
notifyWaitingThreads();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -899,6 +900,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
|
|
||||||
private volatile boolean done;
|
private volatile boolean done;
|
||||||
|
|
||||||
|
private boolean running;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the reader in order to be used. The reader is initialized during the
|
* Initializes the reader in order to be used. The reader is initialized during the
|
||||||
* first connection and when reconnecting due to an abruptly disconnection.
|
* first connection and when reconnecting due to an abruptly disconnection.
|
||||||
|
@ -910,11 +913,13 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
LOGGER.finer(threadName + " start");
|
LOGGER.finer(threadName + " start");
|
||||||
|
running = true;
|
||||||
try {
|
try {
|
||||||
parsePackets();
|
parsePackets();
|
||||||
} finally {
|
} finally {
|
||||||
LOGGER.finer(threadName + " exit");
|
LOGGER.finer(threadName + " exit");
|
||||||
XMPPTCPConnection.this.readerWriterSemaphore.release();
|
running = false;
|
||||||
|
notifyWaitingThreads();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, threadName);
|
}, threadName);
|
||||||
|
@ -931,10 +936,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
* Parse top-level packets in order to process them further.
|
* Parse top-level packets in order to process them further.
|
||||||
*/
|
*/
|
||||||
private void parsePackets() {
|
private void parsePackets() {
|
||||||
boolean initialStreamOpenSend = false;
|
|
||||||
try {
|
try {
|
||||||
openStreamAndResetParser();
|
openStreamAndResetParser();
|
||||||
initialStreamOpenSend = true;
|
|
||||||
XmlPullParser.Event eventType = parser.getEventType();
|
XmlPullParser.Event eventType = parser.getEventType();
|
||||||
while (!done) {
|
while (!done) {
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
|
@ -955,27 +958,17 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
break;
|
break;
|
||||||
case "error":
|
case "error":
|
||||||
StreamError streamError = PacketParserUtils.parseStreamError(parser);
|
StreamError streamError = PacketParserUtils.parseStreamError(parser);
|
||||||
saslFeatureReceived.reportFailure(new StreamErrorException(streamError));
|
// Stream errors are non recoverable, throw this exceptions. Also note that this will set
|
||||||
// Mark the tlsHandled sync point as success, we will use the saslFeatureReceived sync
|
// this exception as current connection exceptions and notify any waiting threads.
|
||||||
// point to report the error, which is checked immediately after tlsHandled in
|
|
||||||
// connectInternal().
|
|
||||||
tlsHandled.reportSuccess();
|
|
||||||
throw new StreamErrorException(streamError);
|
throw new StreamErrorException(streamError);
|
||||||
case "features":
|
case "features":
|
||||||
parseFeaturesAndNotify(parser);
|
parseFeaturesAndNotify(parser);
|
||||||
break;
|
break;
|
||||||
case "proceed":
|
case "proceed":
|
||||||
try {
|
|
||||||
// Secure the connection by negotiating TLS
|
// Secure the connection by negotiating TLS
|
||||||
proceedTLSReceived();
|
proceedTLSReceived();
|
||||||
// Send a new opening stream to the server
|
// Send a new opening stream to the server
|
||||||
openStreamAndResetParser();
|
openStreamAndResetParser();
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
SmackException.SmackWrappedException smackException = new SmackException.SmackWrappedException(e);
|
|
||||||
tlsHandled.reportFailure(smackException);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "failure":
|
case "failure":
|
||||||
String namespace = parser.getNamespace(null);
|
String namespace = parser.getNamespace(null);
|
||||||
|
@ -989,8 +982,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
// situation. It is still possible to authenticate and
|
// situation. It is still possible to authenticate and
|
||||||
// use the connection but using an uncompressed connection
|
// use the connection but using an uncompressed connection
|
||||||
// TODO Parse failure stanza
|
// TODO Parse failure stanza
|
||||||
compressSyncPoint.reportFailure(new SmackException.SmackMessageException(
|
currentSmackException = new SmackException.SmackMessageException("Could not establish compression");
|
||||||
"Could not establish compression"));
|
notifyWaitingThreads();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
parseAndProcessNonza(parser);
|
parseAndProcessNonza(parser);
|
||||||
|
@ -1004,7 +997,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
// Send a new opening stream to the server
|
// Send a new opening stream to the server
|
||||||
openStreamAndResetParser();
|
openStreamAndResetParser();
|
||||||
// Notify that compression is being used
|
// Notify that compression is being used
|
||||||
compressSyncPoint.reportSuccess();
|
compressSyncPoint = true;
|
||||||
|
notifyWaitingThreads();
|
||||||
break;
|
break;
|
||||||
case Enabled.ELEMENT:
|
case Enabled.ELEMENT:
|
||||||
Enabled enabled = ParseStreamManagement.enabled(parser);
|
Enabled enabled = ParseStreamManagement.enabled(parser);
|
||||||
|
@ -1012,7 +1006,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
smSessionId = enabled.getId();
|
smSessionId = enabled.getId();
|
||||||
if (StringUtils.isNullOrEmpty(smSessionId)) {
|
if (StringUtils.isNullOrEmpty(smSessionId)) {
|
||||||
SmackException xmppException = new SmackException.SmackMessageException("Stream Management 'enabled' element with resume attribute but without session id received");
|
SmackException xmppException = new SmackException.SmackMessageException("Stream Management 'enabled' element with resume attribute but without session id received");
|
||||||
smEnabledSyncPoint.reportFailure(xmppException);
|
setCurrentConnectionExceptionAndNotify(xmppException);
|
||||||
throw xmppException;
|
throw xmppException;
|
||||||
}
|
}
|
||||||
smServerMaxResumptionTime = enabled.getMaxResumptionTime();
|
smServerMaxResumptionTime = enabled.getMaxResumptionTime();
|
||||||
|
@ -1022,28 +1016,19 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
clientHandledStanzasCount = 0;
|
clientHandledStanzasCount = 0;
|
||||||
smWasEnabledAtLeastOnce = true;
|
smWasEnabledAtLeastOnce = true;
|
||||||
smEnabledSyncPoint.reportSuccess();
|
smEnabledSyncPoint = true;
|
||||||
LOGGER.fine("Stream Management (XEP-198): successfully enabled");
|
notifyWaitingThreads();
|
||||||
break;
|
break;
|
||||||
case Failed.ELEMENT:
|
case Failed.ELEMENT:
|
||||||
Failed failed = ParseStreamManagement.failed(parser);
|
Failed failed = ParseStreamManagement.failed(parser);
|
||||||
|
if (smResumedSyncPoint == SyncPointState.request_sent) {
|
||||||
|
// This is a <failed/> nonza in a response to resuming a previous stream, failure to do
|
||||||
|
// so is non-fatal as we can simply continue with resource binding in this case.
|
||||||
|
smResumptionFailed = failed;
|
||||||
|
notifyWaitingThreads();
|
||||||
|
} else {
|
||||||
FailedNonzaException xmppException = new FailedNonzaException(failed, failed.getStanzaErrorCondition());
|
FailedNonzaException xmppException = new FailedNonzaException(failed, failed.getStanzaErrorCondition());
|
||||||
// If only XEP-198 would specify different failure elements for the SM
|
setCurrentConnectionExceptionAndNotify(xmppException);
|
||||||
// enable and SM resume failure case. But this is not the case, so we
|
|
||||||
// need to determine if this is a 'Failed' response for either 'Enable'
|
|
||||||
// or 'Resume'.
|
|
||||||
if (smResumedSyncPoint.requestSent()) {
|
|
||||||
smResumedSyncPoint.reportFailure(xmppException);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!smEnabledSyncPoint.requestSent()) {
|
|
||||||
throw new IllegalStateException("Failed element received but SM was not previously enabled");
|
|
||||||
}
|
|
||||||
smEnabledSyncPoint.reportFailure(new SmackException.SmackWrappedException(xmppException));
|
|
||||||
// Report success for last lastFeaturesReceived so that in case a
|
|
||||||
// failed resumption, we can continue with normal resource binding.
|
|
||||||
// See text of XEP-198 5. below Example 11.
|
|
||||||
lastFeaturesReceived.reportSuccess();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Resumed.ELEMENT:
|
case Resumed.ELEMENT:
|
||||||
|
@ -1052,7 +1037,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
throw new StreamIdDoesNotMatchException(smSessionId, resumed.getPrevId());
|
throw new StreamIdDoesNotMatchException(smSessionId, resumed.getPrevId());
|
||||||
}
|
}
|
||||||
// Mark SM as enabled
|
// Mark SM as enabled
|
||||||
smEnabledSyncPoint.reportSuccess();
|
smEnabledSyncPoint = true;
|
||||||
// First, drop the stanzas already handled by the server
|
// First, drop the stanzas already handled by the server
|
||||||
processHandledCount(resumed.getHandledCount());
|
processHandledCount(resumed.getHandledCount());
|
||||||
// Then re-send what is left in the unacknowledged queue
|
// Then re-send what is left in the unacknowledged queue
|
||||||
|
@ -1068,8 +1053,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
requestSmAcknowledgementInternal();
|
requestSmAcknowledgementInternal();
|
||||||
}
|
}
|
||||||
// Mark SM resumption as successful
|
// Mark SM resumption as successful
|
||||||
smResumedSyncPoint.reportSuccess();
|
smResumedSyncPoint = SyncPointState.successful;
|
||||||
LOGGER.fine("Stream Management (XEP-198): Stream resumed");
|
notifyWaitingThreads();
|
||||||
break;
|
break;
|
||||||
case AckAnswer.ELEMENT:
|
case AckAnswer.ELEMENT:
|
||||||
AckAnswer ackAnswer = ParseStreamManagement.ackAnswer(parser);
|
AckAnswer ackAnswer = ParseStreamManagement.ackAnswer(parser);
|
||||||
|
@ -1077,7 +1062,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
break;
|
break;
|
||||||
case AckRequest.ELEMENT:
|
case AckRequest.ELEMENT:
|
||||||
ParseStreamManagement.ackRequest(parser);
|
ParseStreamManagement.ackRequest(parser);
|
||||||
if (smEnabledSyncPoint.wasSuccessful()) {
|
if (smEnabledSyncPoint) {
|
||||||
sendSmAcknowledgementInternal();
|
sendSmAcknowledgementInternal();
|
||||||
} else {
|
} else {
|
||||||
LOGGER.warning("SM Ack Request received while SM is not enabled");
|
LOGGER.warning("SM Ack Request received while SM is not enabled");
|
||||||
|
@ -1101,7 +1086,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
// did re-start the queue again, causing this writer to assume that the queue is not
|
// did re-start the queue again, causing this writer to assume that the queue is not
|
||||||
// shutdown, which results in a call to disconnect().
|
// shutdown, which results in a call to disconnect().
|
||||||
final boolean queueWasShutdown = packetWriter.queue.isShutdown();
|
final boolean queueWasShutdown = packetWriter.queue.isShutdown();
|
||||||
closingStreamReceived.reportSuccess();
|
closingStreamReceived = true;
|
||||||
|
notifyWaitingThreads();
|
||||||
|
|
||||||
if (queueWasShutdown) {
|
if (queueWasShutdown) {
|
||||||
// We received a closing stream element *after* we initiated the
|
// We received a closing stream element *after* we initiated the
|
||||||
|
@ -1137,12 +1123,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
// TODO: Move the call closingStreamReceived.reportFailure(e) into notifyConnectionError?
|
|
||||||
closingStreamReceived.reportFailure(e);
|
|
||||||
// The exception can be ignored if the the connection is 'done'
|
// The exception can be ignored if the the connection is 'done'
|
||||||
// or if the it was caused because the socket got closed. It can not be ignored if it
|
// or if the it was caused because the socket got closed.
|
||||||
// happened before (or while) the initial stream opened was send.
|
if (!done) {
|
||||||
if (!(done || packetWriter.queue.isShutdown()) || !initialStreamOpenSend) {
|
|
||||||
// Close the connection and notify connection listeners of the
|
// Close the connection and notify connection listeners of the
|
||||||
// error.
|
// error.
|
||||||
notifyConnectionError(e);
|
notifyConnectionError(e);
|
||||||
|
@ -1161,12 +1144,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
private final ArrayBlockingQueueWithShutdown<Element> queue = new ArrayBlockingQueueWithShutdown<>(
|
private final ArrayBlockingQueueWithShutdown<Element> queue = new ArrayBlockingQueueWithShutdown<>(
|
||||||
QUEUE_SIZE, true);
|
QUEUE_SIZE, true);
|
||||||
|
|
||||||
/**
|
|
||||||
* Needs to be protected for unit testing purposes.
|
|
||||||
*/
|
|
||||||
protected SynchronizationPoint<NoResponseException> shutdownDone = new SynchronizationPoint<>(
|
|
||||||
XMPPTCPConnection.this, "shutdown completed");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set, the stanza writer is shut down
|
* If set, the stanza writer is shut down
|
||||||
*/
|
*/
|
||||||
|
@ -1184,12 +1161,13 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
*/
|
*/
|
||||||
private boolean shouldBundleAndDefer;
|
private boolean shouldBundleAndDefer;
|
||||||
|
|
||||||
|
private boolean running;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the writer in order to be used. It is called at the first connection and also
|
* Initializes the writer in order to be used. It is called at the first connection and also
|
||||||
* is invoked if the connection is disconnected by an error.
|
* is invoked if the connection is disconnected by an error.
|
||||||
*/
|
*/
|
||||||
void init() {
|
void init() {
|
||||||
shutdownDone.init();
|
|
||||||
shutdownTimestamp = null;
|
shutdownTimestamp = null;
|
||||||
|
|
||||||
if (unacknowledgedStanzas != null) {
|
if (unacknowledgedStanzas != null) {
|
||||||
|
@ -1204,11 +1182,13 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
LOGGER.finer(threadName + " start");
|
LOGGER.finer(threadName + " start");
|
||||||
|
running = true;
|
||||||
try {
|
try {
|
||||||
writePackets();
|
writePackets();
|
||||||
} finally {
|
} finally {
|
||||||
LOGGER.finer(threadName + " exit");
|
LOGGER.finer(threadName + " exit");
|
||||||
XMPPTCPConnection.this.readerWriterSemaphore.release();
|
running = false;
|
||||||
|
notifyWaitingThreads();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, threadName);
|
}, threadName);
|
||||||
|
@ -1256,19 +1236,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
/**
|
/**
|
||||||
* Shuts down the stanza writer. Once this method has been called, no further
|
* Shuts down the stanza writer. Once this method has been called, no further
|
||||||
* packets will be written to the server.
|
* packets will be written to the server.
|
||||||
* @throws InterruptedException if the calling thread was interrupted.
|
|
||||||
*/
|
*/
|
||||||
void shutdown(boolean instant) {
|
void shutdown(boolean instant) {
|
||||||
instantShutdown = instant;
|
instantShutdown = instant;
|
||||||
queue.shutdown();
|
queue.shutdown();
|
||||||
shutdownTimestamp = System.currentTimeMillis();
|
shutdownTimestamp = System.currentTimeMillis();
|
||||||
if (shutdownDone.isNotInInitialState()) {
|
|
||||||
try {
|
|
||||||
shutdownDone.checkIfSuccessOrWait();
|
|
||||||
} catch (NoResponseException | InterruptedException e) {
|
|
||||||
LOGGER.log(Level.WARNING, "shutdownDone was not marked as successful by the writer thread", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1370,7 +1342,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
writer.write(packet.toXML().toString());
|
writer.write(packet.toXML().toString());
|
||||||
}
|
}
|
||||||
writer.flush();
|
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
LOGGER.log(Level.WARNING,
|
LOGGER.log(Level.WARNING,
|
||||||
|
@ -1407,9 +1378,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
} else {
|
} else {
|
||||||
LOGGER.log(Level.FINE, "Ignoring Exception in writePackets()", e);
|
LOGGER.log(Level.FINE, "Ignoring Exception in writePackets()", e);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
LOGGER.fine("Reporting shutdownDone success in writer thread");
|
|
||||||
shutdownDone.reportSuccess();
|
|
||||||
}
|
}
|
||||||
// Delay notifyConnectionError after shutdownDone has been reported in the finally block.
|
// Delay notifyConnectionError after shutdownDone has been reported in the finally block.
|
||||||
if (writerException != null) {
|
if (writerException != null) {
|
||||||
|
@ -1721,7 +1689,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
* @return true if Stream Management was negotiated.
|
* @return true if Stream Management was negotiated.
|
||||||
*/
|
*/
|
||||||
public boolean isSmEnabled() {
|
public boolean isSmEnabled() {
|
||||||
return smEnabledSyncPoint.wasSuccessful();
|
return smEnabledSyncPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1730,7 +1698,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
* @return true if the stream was resumed.
|
* @return true if the stream was resumed.
|
||||||
*/
|
*/
|
||||||
public boolean streamWasResumed() {
|
public boolean streamWasResumed() {
|
||||||
return smResumedSyncPoint.wasSuccessful();
|
return smResumedSyncPoint == SyncPointState.successful;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.jivesoftware.smack.ConnectionConfiguration;
|
||||||
* </p>
|
* </p>
|
||||||
* <pre>
|
* <pre>
|
||||||
* {@code
|
* {@code
|
||||||
* XMPPTCPConnectionConfiguration conf = XMPPConnectionConfiguration.builder()
|
* XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder()
|
||||||
* .setXmppDomain("example.org").setUsernameAndPassword("user", "password")
|
* .setXmppDomain("example.org").setUsernameAndPassword("user", "password")
|
||||||
* .setCompressionEnabled(false).build();
|
* .setCompressionEnabled(false).build();
|
||||||
* XMPPTCPConnection connection = new XMPPTCPConnection(conf);
|
* XMPPTCPConnection connection = new XMPPTCPConnection(conf);
|
||||||
|
|
|
@ -47,17 +47,13 @@ import javax.net.ssl.SSLSession;
|
||||||
|
|
||||||
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.ConnectionException;
|
|
||||||
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
|
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
|
||||||
import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
|
import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
|
||||||
import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
|
import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
|
||||||
import org.jivesoftware.smack.SmackException.SmackWrappedException;
|
import org.jivesoftware.smack.SmackException.SmackCertificateException;
|
||||||
import org.jivesoftware.smack.SmackFuture;
|
import org.jivesoftware.smack.SmackFuture;
|
||||||
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
|
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
|
||||||
import org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment;
|
import org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment;
|
||||||
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.XmppInputOutputFilter;
|
import org.jivesoftware.smack.XmppInputOutputFilter;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
|
||||||
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.LookupRemoteConnectionEndpointsStateDescriptor;
|
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.LookupRemoteConnectionEndpointsStateDescriptor;
|
||||||
|
@ -591,7 +587,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
|
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
|
||||||
// Assert that there are no stale discovred endpoints prior performing the lookup.
|
// Assert that there are no stale discovered endpoints prior performing the lookup.
|
||||||
assert discoveredTcpEndpoints == null;
|
assert discoveredTcpEndpoints == null;
|
||||||
|
|
||||||
List<SmackFuture<LookupConnectionEndpointsResult, Exception>> futures = new ArrayList<>(2);
|
List<SmackFuture<LookupConnectionEndpointsResult, Exception>> futures = new ArrayList<>(2);
|
||||||
|
@ -744,23 +740,14 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
||||||
throws InterruptedException, ConnectionUnexpectedTerminatedException, NotConnectedException,
|
throws InterruptedException, IOException, SmackException, XMPPException {
|
||||||
NoResponseException, IOException {
|
|
||||||
// The fields inetSocketAddress and failedAddresses are handed over from LookupHostAddresses to
|
// The fields inetSocketAddress and failedAddresses are handed over from LookupHostAddresses to
|
||||||
// ConnectingToHost.
|
// ConnectingToHost.
|
||||||
ConnectionAttemptState connectionAttemptState = new ConnectionAttemptState(connectionInternal, discoveredTcpEndpoints,
|
ConnectionAttemptState connectionAttemptState = new ConnectionAttemptState(connectionInternal, discoveredTcpEndpoints,
|
||||||
this);
|
this);
|
||||||
connectionAttemptState.establishTcpConnection();
|
StateTransitionResult.Failure failure = connectionAttemptState.establishTcpConnection();
|
||||||
|
if (failure != null) {
|
||||||
try {
|
return failure;
|
||||||
connectionAttemptState.tcpConnectionEstablishedSyncPoint.checkIfSuccessOrWaitOrThrow();
|
|
||||||
} catch (ConnectionException | NoResponseException e) {
|
|
||||||
// TODO: It is not really elegant that we catch the exception here. Ideally ConnectionAttemptState would
|
|
||||||
// simply return a StateTranstionResult.FailureCausedByException.
|
|
||||||
return new StateTransitionResult.FailureCausedByException<>(e);
|
|
||||||
} catch (SmackWrappedException e) {
|
|
||||||
// Should never throw SmackWrappedException.
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
socketChannel = connectionAttemptState.socketChannel;
|
socketChannel = connectionAttemptState.socketChannel;
|
||||||
|
@ -858,8 +845,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
|
||||||
throws SmackWrappedException, FailedNonzaException, IOException, InterruptedException,
|
throws IOException, InterruptedException, SmackException, XMPPException {
|
||||||
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
|
|
||||||
connectionInternal.sendAndWaitForResponse(StartTls.INSTANCE, TlsProceed.class, TlsFailure.class);
|
connectionInternal.sendAndWaitForResponse(StartTls.INSTANCE, TlsProceed.class, TlsFailure.class);
|
||||||
|
|
||||||
SmackTlsContext smackTlsContext = connectionInternal.getSmackTlsContext();
|
SmackTlsContext smackTlsContext = connectionInternal.getSmackTlsContext();
|
||||||
|
@ -881,7 +867,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
try {
|
try {
|
||||||
tlsState.waitForHandshakeFinished();
|
tlsState.waitForHandshakeFinished();
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
throw new SmackWrappedException(e);
|
throw new SmackCertificateException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after TLS established");
|
connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after TLS established");
|
||||||
|
@ -1166,43 +1152,20 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
private void handleSslException(SSLException e) {
|
private void handleSslException(SSLException e) {
|
||||||
handshakeException = e;
|
handshakeException = e;
|
||||||
handshakeStatus = TlsHandshakeStatus.failed;
|
handshakeStatus = TlsHandshakeStatus.failed;
|
||||||
synchronized (this) {
|
connectionInternal.notifyWaitingThreads();
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onHandshakeFinished() {
|
private void onHandshakeFinished() {
|
||||||
handshakeStatus = TlsHandshakeStatus.successful;
|
handshakeStatus = TlsHandshakeStatus.successful;
|
||||||
synchronized (this) {
|
connectionInternal.notifyWaitingThreads();
|
||||||
notifyAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isHandshakeFinished() {
|
private boolean isHandshakeFinished() {
|
||||||
return handshakeStatus == TlsHandshakeStatus.successful || handshakeStatus == TlsHandshakeStatus.failed;
|
return handshakeStatus == TlsHandshakeStatus.successful || handshakeStatus == TlsHandshakeStatus.failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void waitForHandshakeFinished() throws InterruptedException, CertificateException, SSLException, ConnectionUnexpectedTerminatedException, NoResponseException {
|
private void waitForHandshakeFinished() throws InterruptedException, CertificateException, SSLException, SmackException, XMPPException {
|
||||||
final long deadline = System.currentTimeMillis() + connectionInternal.connection.getReplyTimeout();
|
connectionInternal.waitForCondition(() -> isHandshakeFinished(), "TLS handshake to finish");
|
||||||
|
|
||||||
Exception currentConnectionException = null;
|
|
||||||
synchronized (this) {
|
|
||||||
while (!isHandshakeFinished()
|
|
||||||
&& (currentConnectionException = connectionInternal.getCurrentConnectionException()) == null) {
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
if (now >= deadline)
|
|
||||||
break;
|
|
||||||
wait(deadline - now);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentConnectionException != null) {
|
|
||||||
throw new SmackException.ConnectionUnexpectedTerminatedException(currentConnectionException);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isHandshakeFinished()) {
|
|
||||||
throw NoResponseException.newWith(connectionInternal.connection, "TLS handshake to finish");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handshakeStatus == TlsHandshakeStatus.failed) {
|
if (handshakeStatus == TlsHandshakeStatus.failed) {
|
||||||
throw handshakeException;
|
throw handshakeException;
|
||||||
|
@ -1235,7 +1198,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void waitUntilInputOutputClosed() throws IOException, CertificateException, InterruptedException,
|
public void waitUntilInputOutputClosed() throws IOException, CertificateException, InterruptedException,
|
||||||
ConnectionUnexpectedTerminatedException, NoResponseException {
|
SmackException, XMPPException {
|
||||||
waitForHandshakeFinished();
|
waitForHandshakeFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2014-2019 Florian Schmaus
|
* Copyright 2014-2020 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -124,8 +124,6 @@ public class PacketWriterTest {
|
||||||
// Not really cool, but may increases the chances for 't' to block in sendStanza.
|
// Not really cool, but may increases the chances for 't' to block in sendStanza.
|
||||||
Thread.sleep(250);
|
Thread.sleep(250);
|
||||||
|
|
||||||
// Set to true for testing purposes, so that shutdown() won't wait packet writer
|
|
||||||
pw.shutdownDone.reportSuccess();
|
|
||||||
// Shutdown the packetwriter, this will also interrupt the writer thread, which is what we hope to happen in the
|
// Shutdown the packetwriter, this will also interrupt the writer thread, which is what we hope to happen in the
|
||||||
// thread created above.
|
// thread created above.
|
||||||
pw.shutdown(false);
|
pw.shutdown(false);
|
||||||
|
|
2
version
2
version
|
@ -1 +1 @@
|
||||||
4.4.0-alpha4-SNAPSHOT
|
4.4.0-alpha5-SNAPSHOT
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue