1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-09-14 03:29:38 +02:00

Compare commits

...

30 commits

Author SHA1 Message Date
Florian Schmaus
d74f19e26a Smack 4.4.0-alpha5-SNAPSHOT 2020-06-14 23:00:21 +02:00
Florian Schmaus
b6be7a9694 Smack 4.4.0-alpha4 2020-06-14 22:23:42 +02:00
Florian Schmaus
3f9ca68134 Delete TypedCloneable 2020-06-14 17:38:51 +02:00
Florian Schmaus
18c2c37ad0 Rename XmlUnitUtils to XmlAssertUtil 2020-06-14 16:53:21 +02:00
Florian Schmaus
9d6665735f [pubsub] Rework NodeExtension.toXML() 2020-06-14 16:52:13 +02:00
Florian Schmaus
c689bef7ec Add static QNAME field to Compressed, Failure and Tls(Failure|Proceed) 2020-06-14 16:51:28 +02:00
Florian Schmaus
cf1f533fff [tcp] Mark SM sync points as volatile 2020-06-12 21:16:05 +02:00
Florian Schmaus
fd1b49ca8f [tcp] Set sync points before they are used, and not in init() 2020-06-12 18:15:40 +02:00
Florian Schmaus
843c8dd7fe [tcp] Add missing notifyWaitingThreads() after receiving SM <failed/> 2020-06-12 16:35:32 +02:00
damencho
8e337d7810 [muc] Make providesMucService() use the KNOWN_MUC_SERVICES cache
This reduces the number of disco#info queries on MUC join in some
situations.
2020-06-12 15:54:14 +02:00
Florian Schmaus
f1319d1c8b Move getters from Message to MessageView 2020-06-12 09:17:54 +02:00
Florian Schmaus
6a617af158 Merge branch 'master' of github.com:igniterealtime/Smack 2020-06-04 22:51:17 +02:00
adiaholic
1d49de6b60 Add integration test for MultiUserChat
SMACK-888 has a mention of unreachable code and is now solved
under commit 0f7b7df.
This integration test tests those class entities which were to
be set by previously stated unreachable code.
2020-06-04 22:46:52 +02:00
Florian Schmaus
afbe833a97
Merge pull request #395 from adiaholic/docFix
Correct documentation
2020-06-02 22:07:35 +02:00
Florian Schmaus
ccbc0922ad [core] Fix comment 2020-06-02 10:05:56 +02:00
Florian Schmaus
e2a196fa52 [sinttest] Improve status output 2020-05-31 21:08:14 +02:00
Florian Schmaus
6c84356278 [sinttest] Recycle low-level test connections 2020-05-31 21:01:57 +02:00
Florian Schmaus
dd4cd8cede [sinttest] Disconnect unrecycleable connections 2020-05-31 21:01:32 +02:00
Florian Schmaus
2900cc2274 [sinttest] Disconnect connection in LoginIntegrationTest
So that it will cause an connectionClosedOnError() callback, caused by
an connection-timeout stream error.
2020-05-31 21:00:42 +02:00
Florian Schmaus
7d129d6f6c [tcp] Cleanup handling of stream errors in XMPPTCPConnection
There is no need to notify waiting threads, throwing the stream error
will also notify them. Also settings tlsHandled to true is no longer
necessary.
2020-05-31 19:49:42 +02:00
Florian Schmaus
b7465e8200 [tcp] Do not needlessly wait for closing stream tag 2020-05-31 19:49:42 +02:00
Florian Schmaus
84b7adb764 [tcp] Remove javadoc throws annotation
This method does no longer throw.
2020-05-31 19:49:42 +02:00
Florian Schmaus
22baa74298 [tcp] Remove flush() in writer thread
We will flush the stream after the closing stream tag has been written
anyway. No need to do it here.
2020-05-31 19:49:42 +02:00
Florian Schmaus
81f10b0c5b [core] Synchronize notifyConnectionError()
Synchronize notifyConnectionError() so that only one exception is
handled and remove the ASYNC_BUT_ORDERED usage here. The
ASYNC_BUT_ORDERED was added with 7d2c3ac9f ("Do not call synchronized
methods in reader/writer thread"), but is no longer necessary, since
the Semaphores where replaced with conditions in the previous commit.
2020-05-31 19:49:40 +02:00
Florian Schmaus
57961a8cc1 Remove SynchronizationPoint
This continues the design started with e98d42790 ("SmackReactor/NIO,
Java8/Android19, Pretty print XML, FSM connections"), where the
exceptions that caused an operation to fail, are not recorded within
SynchronizationPoint but within the connection instance itself.
2020-05-31 19:48:47 +02:00
Aditya Borikar
d639e0bc4c Some more docFix es 2020-05-31 01:10:29 +05:30
Florian Schmaus
b1a4ccfae8 [core] Do not weakly reference "channel selected" callback
Since d65f2c932 ("Bump Error Prone version to 2.3.4 and fix new bug
patterns") the channel selected callback is no longer a final field of
the connection instance, hence it may be come null even if the
connection instance is still strongly referenced. Also the
ConnectionAttemptState class uses simply a lambda as callback, which
is also not strongly referenced otherwise.

The "channel selected" callback was wrapped in weak reference, so that
connection instances could get gc'ed if they are still connected but
the user lost all references to them. In this case, the weak reference
to the connection instance would become 'null' and
selectionKey.cancel() would be called.

This change means that a socket and its selection key of a "leaked"
connected connection instance continues to be part of the reactor. But
this may not be that bad: first, users are expected to manager their
connection instances, and disconnect them before they are
discarded. And secondly, at some point the connection likely will get
disconnected, and in this case, the socket and its selection key will
be removed from the reactor.
2020-05-30 19:45:59 +02:00
Aditya Borikar
223d58527b Use correct class name in docs
XMPPConnectionConfiguration/XMPPTCPConnectionConfiguration
2020-05-28 03:29:37 +05:30
Florian Schmaus
eae8acb856 [gradle] Add 'sinttestAll' task 2020-05-26 09:54:06 +02:00
Florian Schmaus
10c6e5d121 [openpgp] Use default constructor of DummyConnection in unit tests 2020-05-26 09:53:43 +02:00
90 changed files with 841 additions and 1161 deletions

View file

@ -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'

View file

@ -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) {
if (!isConnected()) { synchronized (notifyConnectionErrorMonitor) {
LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception, if (!isConnected()) {
exception); LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception,
return; exception);
} 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);
// 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
// case the Exception is a StreamErrorException.
instantShutdown();
for (StanzaCollector collector : collectors) { for (StanzaCollector collector : collectors) {
collector.notifyConnectionError(exception); 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
// possible. Note that a connection listener of e.g. XMPPTCPConnection will drop the SM state in
// case the Exception is a StreamErrorException.
instantShutdown();
}
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();

View file

@ -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;
}
}
} }

View file

@ -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,13 +367,8 @@ 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();
}
} }
} }
@ -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() {

View file

@ -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,
}
}

View file

@ -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();

View file

@ -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);
} }
} }

View file

@ -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);

View file

@ -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();

View file

@ -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,

View file

@ -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);

View file

@ -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;

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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);
} }

View file

@ -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();
}
} }

View file

@ -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();

View file

@ -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();
}
} }

View file

@ -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() {
} }

View file

@ -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() {
} }

View file

@ -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}.

View file

@ -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();
} }

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);
} }
} }

View file

@ -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();

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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();

View file

@ -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) {

View file

@ -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,12 +336,8 @@ 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)) { throw new NotAMucServiceException(this);
KNOWN_MUC_SERVICES.put(mucService, null);
} else {
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"

View file

@ -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;
} }
/** /**

View file

@ -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.append(items);
xml.closeElement(this);
return xml;
} }
xml.rightAngleBracket();
xml.append(items);
xml.closeElement(this);
} }
} }

View file

@ -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();
}
} }
} }

View file

@ -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;
} }
} }

View file

@ -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

View file

@ -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("</");
builder.append(getElementName());
builder.append('>');
return builder.toString();
} }
xml.closeElement(this);
} }
@Override @Override

View file

@ -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

View file

@ -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);
} }
} }

View file

@ -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

View file

@ -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();
} }
} }

View file

@ -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;
} }
} }

View file

@ -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;
} }
} }

View file

@ -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);
} }
} }

View file

@ -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;
} }
} }

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);
} }
} }

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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.

View file

@ -58,11 +58,15 @@ public class LoginIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
AbstractXMPPConnection connection = getUnconnectedConnection(); AbstractXMPPConnection connection = getUnconnectedConnection();
connection.connect(); connection.connect();
SASLErrorException saslErrorException = assertThrows(SASLErrorException.class, try {
() -> connection.login(nonExistentUserString, invalidPassword)); SASLErrorException saslErrorException = assertThrows(SASLErrorException.class,
() -> 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();
}
} }
} }

View file

@ -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);
} }
} }

View file

@ -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);
}
} }

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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"));

View file

@ -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);

View file

@ -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,14 +494,19 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
private void shutdown(boolean instant) { private void shutdown(boolean instant) {
// First shutdown the writer, this will result in a closing stream element getting send to // The writer thread may already been finished at this point, for example when the connection is in the
// the server // disconnected-but-resumable state. There is no need to wait for the closing stream tag from the server in this
LOGGER.finer("PacketWriter shutdown()"); // case.
packetWriter.shutdown(instant); if (!packetWriter.done()) {
LOGGER.finer("PacketWriter has been shut down"); // First shutdown the writer, this will result in a closing stream element getting send to
// the server
LOGGER.finer("PacketWriter shutdown()");
packetWriter.shutdown(instant);
LOGGER.finer("PacketWriter has been shut down");
if (!instant) { if (!instant) {
waitForClosingStreamTagFromServer(); waitForClosingStreamTagFromServer();
}
} }
LOGGER.finer("PacketReader shutdown()"); LOGGER.finer("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);
FailedNonzaException xmppException = new FailedNonzaException(failed, failed.getStanzaErrorCondition()); if (smResumedSyncPoint == SyncPointState.request_sent) {
// If only XEP-198 would specify different failure elements for the SM // This is a <failed/> nonza in a response to resuming a previous stream, failure to do
// enable and SM resume failure case. But this is not the case, so we // so is non-fatal as we can simply continue with resource binding in this case.
// need to determine if this is a 'Failed' response for either 'Enable' smResumptionFailed = failed;
// or 'Resume'. notifyWaitingThreads();
if (smResumedSyncPoint.requestSent()) { } else {
smResumedSyncPoint.reportFailure(xmppException); FailedNonzaException xmppException = new FailedNonzaException(failed, failed.getStanzaErrorCondition());
} setCurrentConnectionExceptionAndNotify(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;
} }
/** /**

View file

@ -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);

View file

@ -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();
} }

View file

@ -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);

View file

@ -1 +1 @@
4.4.0-alpha4-SNAPSHOT 4.4.0-alpha5-SNAPSHOT