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

Compare commits

..

No commits in common. "d74f19e26a03a445292af9f67077422cdc959566" and "ccc785062e9b04513302c898e3015c2ff2d7fb91" have entirely different histories.

90 changed files with 1161 additions and 841 deletions

View file

@ -662,14 +662,6 @@ 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,7 +111,6 @@ 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;
@ -175,12 +174,6 @@ 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.
@ -278,29 +271,30 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
*/ */
protected Writer writer; protected Writer writer;
protected SmackException currentSmackException; protected final SynchronizationPoint<SmackException> tlsHandled = new SynchronizationPoint<>(this, "establishing TLS");
protected XMPPException currentXmppException;
protected boolean tlsHandled;
/** /**
* Set to <code>true</code> if the last features stanza from the server has been parsed. A XMPP connection * Set to success 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 boolean lastFeaturesReceived; protected final SynchronizationPoint<SmackException> lastFeaturesReceived = new SynchronizationPoint<>(
AbstractXMPPConnection.this, "last stream features received from server");
/** /**
* Set to <code>true</code> if the SASL feature has been received. * Set to success if the SASL feature has been received.
*/ */
protected boolean saslFeatureReceived; protected final SynchronizationPoint<XMPPException> saslFeatureReceived = new SynchronizationPoint<>(
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 boolean closingStreamReceived; protected final SynchronizationPoint<Exception> closingStreamReceived = new SynchronizationPoint<>(
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.
@ -375,6 +369,8 @@ 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<>();
@ -490,10 +486,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
public abstract boolean isUsingCompression(); public abstract boolean isUsingCompression();
protected void initState() { protected void initState() {
currentSmackException = null; saslFeatureReceived.init();
currentXmppException = null; lastFeaturesReceived.init();
saslFeatureReceived = lastFeaturesReceived = tlsHandled = false; tlsHandled.init();
// 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.
} }
@ -516,7 +512,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// Reset the connection state // Reset the connection state
initState(); initState();
closingStreamReceived = false; closingStreamReceived.init();
streamId = null; streamId = null;
try { try {
@ -661,82 +657,15 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return streamId; return streamId;
} }
protected final void throwCurrentConnectionException() throws SmackException, XMPPException { protected Resourcepart bindResourceAndEstablishSession(Resourcepart resource) throws XMPPErrorException,
if (currentSmackException != null) { SmackException, InterruptedException {
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");
waitForConditionOrThrowConnectionException(() -> lastFeaturesReceived, "last stream features received from server"); lastFeaturesReceived.checkIfSuccessOrWaitOrThrow();
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
@ -963,8 +892,6 @@ 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.
@ -972,31 +899,41 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* @param exception the exception that causes the connection close event. * @param exception the exception that causes the connection close event.
*/ */
protected final void notifyConnectionError(final Exception exception) { protected final void notifyConnectionError(final Exception exception) {
synchronized (notifyConnectionErrorMonitor) { if (!isConnected()) {
if (!isConnected()) { LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception,
LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception, exception);
exception); return;
return; }
}
// Note that we first have to set the current connection exception and notify waiting threads, as one of them ASYNC_BUT_ORDERED.performAsyncButOrdered(this, () -> {
// could hold the instance lock, which we also need later when calling instantShutdown(). currentConnectionException = exception;
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()");
} });
} }
/** /**
@ -1010,13 +947,19 @@ 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 {
waitForConditionOrThrowConnectionException(() -> closingStreamReceived, "closing stream tag from the server"); // After we send the closing stream element, check if there was already a
} catch (InterruptedException | SmackException | XMPPException e) { // closing stream element sent by the server or wait with a timeout for a
LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, e); // closing stream element to be received from the server.
return false; exception = closingStreamReceived.checkIfSuccessOrWait();
} catch (InterruptedException | NoResponseException e) {
exception = e;
} }
return true; if (exception != null) {
LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, exception);
}
return exception == null;
} }
@Override @Override
@ -1874,8 +1817,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 = saslFeatureReceived = true; tlsHandled.reportSuccess();
notifyWaitingThreads(); saslFeatureReceived.reportSuccess();
} }
} }
@ -1884,10 +1827,9 @@ 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 where the last stream features from the server, either it did not contain // This was was last features from the server is either it did not contain
// compression or we disabled it. // compression or if we disabled it
lastFeaturesReceived = true; lastFeaturesReceived.reportSuccess();
notifyWaitingThreads();
} }
} }
afterFeaturesReceived(); afterFeaturesReceived();

View file

@ -16,7 +16,6 @@
*/ */
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;
@ -372,6 +371,15 @@ 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 {
/** /**
@ -479,19 +487,4 @@ 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,6 +17,7 @@
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;
@ -367,8 +368,13 @@ 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.channelSelectedCallback; ChannelSelectedCallback channelSelectedCallback = selectionKeyAttachment.weaeklyReferencedChannelSelectedCallback.get();
channelSelectedCallback.onChannelSelected(channel, selectionKey); if (channelSelectedCallback != null) {
channelSelectedCallback.onChannelSelected(channel, selectionKey);
}
else {
selectionKey.cancel();
}
} }
} }
@ -416,11 +422,11 @@ public class SmackReactor {
} }
public static final class SelectionKeyAttachment { public static final class SelectionKeyAttachment {
private final ChannelSelectedCallback channelSelectedCallback; private final WeakReference<ChannelSelectedCallback> weaeklyReferencedChannelSelectedCallback;
private final AtomicBoolean reactorThreadRacing = new AtomicBoolean(); private final AtomicBoolean reactorThreadRacing = new AtomicBoolean();
private SelectionKeyAttachment(ChannelSelectedCallback channelSelectedCallback) { private SelectionKeyAttachment(ChannelSelectedCallback channelSelectedCallback) {
this.channelSelectedCallback = channelSelectedCallback; this.weaeklyReferencedChannelSelectedCallback = new WeakReference<>(channelSelectedCallback);
} }
private void setRacing() { private void setRacing() {

View file

@ -0,0 +1,352 @@
/**
*
* 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,8 +67,7 @@ public interface XmppInputOutputFilter {
default void closeInputOutput() { default void closeInputOutput() {
} }
default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException, default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException, InterruptedException, SmackException {
InterruptedException, SmackException, XMPPException {
} }
Object getStats(); Object getStats();

View file

@ -35,6 +35,7 @@ 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;
@ -77,7 +78,6 @@ 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,8 +142,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override @Override
public void onStreamClosed() { public void onStreamClosed() {
ModularXmppClientToServerConnection.this.closingStreamReceived = true; ModularXmppClientToServerConnection.this.closingStreamReceived.reportSuccess();
notifyWaitingThreads();
} }
@Override @Override
@ -178,7 +177,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override @Override
public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException, public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
SmackException, XMPPException { ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
ModularXmppClientToServerConnection.this.newStreamOpenWaitForFeaturesSequence(waitFor); ModularXmppClientToServerConnection.this.newStreamOpenWaitForFeaturesSequence(waitFor);
} }
@ -199,14 +198,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
} }
@Override @Override
public void waitForCondition(Supplier<Boolean> condition, String waitFor) public Exception getCurrentConnectionException() {
throws InterruptedException, SmackException, XMPPException { return ModularXmppClientToServerConnection.this.currentConnectionException;
ModularXmppClientToServerConnection.this.waitForConditionOrThrowConnectionException(condition, waitFor);
}
@Override
public void notifyWaitingThreads() {
ModularXmppClientToServerConnection.this.notifyWaitingThreads();
} }
@Override @Override
@ -270,13 +263,14 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
revertedState.resetState(); revertedState.resetState();
} }
protected void walkStateGraph(WalkStateGraphContext walkStateGraphContext) protected void walkStateGraph(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
throws XMPPException, IOException, SmackException, InterruptedException { SASLErrorException, FailedNonzaException, 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 (IOException | SmackException | InterruptedException | XMPPException e) { } catch (XMPPErrorException | SASLErrorException | FailedNonzaException | IOException | SmackException
| InterruptedException e) {
currentStateVertex = previousStateVertex; currentStateVertex = previousStateVertex;
// Unwind the state. // Unwind the state.
State revertedState = currentStateVertex.getElement(); State revertedState = currentStateVertex.getElement();
@ -285,8 +279,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
} }
} }
private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext) private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
throws IOException, SmackException, InterruptedException, XMPPException { SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
// 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();
@ -359,19 +353,21 @@ 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. * Attempt to enter a state. Note that this method may return <code>null</code> if this state can be safely ignored 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 XMPPException if an XMPP protocol error was received. * @throws XMPPErrorException 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, XMPPException, WalkStateGraphContext walkStateGraphContext) throws SmackException, XMPPErrorException,
IOException, InterruptedException { SASLErrorException, IOException, InterruptedException, FailedNonzaException {
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();
@ -404,7 +400,8 @@ 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 | IOException | InterruptedException | XMPPException e) { } catch (SmackException | XMPPErrorException | SASLErrorException | IOException | InterruptedException
| 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);
@ -477,7 +474,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
try { try {
walkStateGraph(context); walkStateGraph(context);
} catch (IOException | SmackException | InterruptedException | XMPPException e) { } catch (XMPPErrorException | SASLErrorException | IOException | SmackException | InterruptedException
| 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);
} }
} }
@ -493,7 +491,9 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override @Override
protected void afterFeaturesReceived() { protected void afterFeaturesReceived() {
featuresReceived = true; featuresReceived = true;
notifyWaitingThreads(); synchronized (this) {
notifyAll();
}
} }
protected void parseAndProcessElement(String element) { protected void parseAndProcessElement(String element) {
@ -522,10 +522,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
break; break;
case "error": case "error":
StreamError streamError = PacketParserUtils.parseStreamError(parser, null); StreamError streamError = PacketParserUtils.parseStreamError(parser, null);
StreamErrorException streamErrorException = new StreamErrorException(streamError); saslFeatureReceived.reportFailure(new StreamErrorException(streamError));
currentXmppException = streamErrorException; throw new StreamErrorException(streamError);
notifyWaitingThreads();
throw streamErrorException;
case "features": case "features":
parseFeatures(parser); parseFeatures(parser);
afterFeaturesReceived(); afterFeaturesReceived();
@ -552,12 +550,25 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
} }
protected void waitForFeaturesReceived(String waitFor) protected void waitForFeaturesReceived(String waitFor)
throws InterruptedException, SmackException, XMPPException { throws InterruptedException, ConnectionUnexpectedTerminatedException, NoResponseException {
waitForConditionOrThrowConnectionException(() -> featuresReceived, waitFor); long waitStartMs = System.currentTimeMillis();
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,
SmackException, XMPPException { ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
prepareToWaitForFeaturesReceived(); prepareToWaitForFeaturesReceived();
sendStreamOpen(); sendStreamOpen();
waitForFeaturesReceived(waitFor); waitForFeaturesReceived(waitFor);
@ -752,7 +763,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override @Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws IOException, SmackException, InterruptedException, XMPPException { throws XMPPErrorException, SASLErrorException, IOException, SmackException,
InterruptedException {
prepareToWaitForFeaturesReceived(); prepareToWaitForFeaturesReceived();
LoginContext loginContext = walkStateGraphContext.getLoginContext(); LoginContext loginContext = walkStateGraphContext.getLoginContext();
@ -801,12 +813,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override @Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws IOException, SmackException, InterruptedException, XMPPException { throws XMPPErrorException, SASLErrorException, IOException, SmackException,
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 = true; lastFeaturesReceived.reportSuccess();
notifyWaitingThreads();
LoginContext loginContext = walkStateGraphContext.getLoginContext(); LoginContext loginContext = walkStateGraphContext.getLoginContext();
Resourcepart resource = bindResourceAndEstablishSession(loginContext.resource); Resourcepart resource = bindResourceAndEstablishSession(loginContext.resource);
@ -902,7 +914,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override @Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) { public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
closingStreamReceived = false; closingStreamReceived.init();
boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(StreamClose.INSTANCE); boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(StreamClose.INSTANCE);
@ -924,7 +936,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 | XMPPException e) { } catch (IOException | CertificateException | InterruptedException | SmackException e) {
LOGGER.log(Level.WARNING, "waitUntilInputOutputClosed() threw", e); LOGGER.log(Level.WARNING, "waitUntilInputOutputClosed() threw", e);
} }
} }

View file

@ -22,12 +22,11 @@ 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; 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.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;
@ -39,7 +38,6 @@ 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 {
@ -100,7 +98,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,
NoResponseException, NotConnectedException, SmackException, XMPPException; ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException;
public abstract SmackTlsContext getSmackTlsContext(); public abstract SmackTlsContext getSmackTlsContext();
@ -110,9 +108,7 @@ public abstract class ModularXmppClientToServerConnectionInternal {
public abstract void asyncGo(Runnable runnable); public abstract void asyncGo(Runnable runnable);
public abstract void waitForCondition(Supplier<Boolean> condition, String waitFor) throws InterruptedException, SmackException, XMPPException; public abstract Exception getCurrentConnectionException();
public abstract void notifyWaitingThreads();
public abstract void setCompressionEnabled(boolean compressionEnabled); public abstract void setCompressionEnabled(boolean compressionEnabled);

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2014-2020 Florian Schmaus * Copyright © 2014-2015 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,15 +16,12 @@
*/ */
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-2020 Florian Schmaus * Copyright 2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,8 +18,6 @@ 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;
@ -28,7 +26,6 @@ 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,8 +17,10 @@
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; import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.SmackException.NoResponseException;
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;
@ -88,7 +90,8 @@ public class CompressionModule extends ModularXmppClientToServerConnectionModule
@Override @Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws InterruptedException, SmackException, XMPPException { throws NoResponseException, NotConnectedException, FailedNonzaException, InterruptedException,
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,9 +19,11 @@ 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; import org.jivesoftware.smack.XMPPException.FailedNonzaException;
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
@ -51,7 +53,8 @@ public abstract class State {
} }
public abstract StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) public abstract StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws IOException, SmackException, InterruptedException, XMPPException; throws XMPPErrorException, SASLErrorException, IOException, SmackException,
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 // If stateClassConstructor is null here, then you probably forgot to override the 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,14 +67,6 @@ 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,16 +17,20 @@
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;
@ -58,7 +62,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 { implements MessageView, TypedCloneable<Message> {
public static final String ELEMENT = "message"; public static final String ELEMENT = "message";
public static final String BODY = "body"; public static final String BODY = "body";
@ -182,6 +186,59 @@ 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.
@ -210,7 +267,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 = Stanza.determineLanguage(this, language); language = determineLanguage(language);
List<Subject> currentSubjects = getExtensions(Subject.class); List<Subject> currentSubjects = getExtensions(Subject.class);
for (Subject currentSubject : currentSubjects) { for (Subject currentSubject : currentSubjects) {
@ -233,7 +290,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 = Stanza.determineLanguage(this, language); language = determineLanguage(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);
@ -254,6 +311,77 @@ 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.
* *
@ -303,7 +431,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 = Stanza.determineLanguage(this, language); language = determineLanguage(language);
removeBody(language); removeBody(language);
@ -322,7 +450,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 = Stanza.determineLanguage(this, language); language = determineLanguage(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)) {
@ -348,6 +476,37 @@ 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.
@ -361,6 +520,18 @@ 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;
@ -371,16 +542,6 @@ 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();
@ -419,10 +580,7 @@ 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-2020 Florian Schmaus * Copyright 2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,8 +16,6 @@
*/ */
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
@ -35,8 +33,4 @@ 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 2003-2007 Jive Software, 2019-2020 Florian Schmaus * Copyright 2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,15 +16,6 @@
*/ */
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 {
/** /**
@ -35,158 +26,4 @@ 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 { implements PresenceView, TypedCloneable<Presence> {
public static final String ELEMENT = "presence"; public static final String ELEMENT = "presence";
@ -282,16 +282,6 @@ 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();
@ -353,10 +343,7 @@ 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);
@ -367,10 +354,7 @@ 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,18 +575,4 @@ 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-2020 Florian Schmaus * Copyright 2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,15 +16,12 @@
*/ */
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-2020 Florian Schmaus * Copyright 2018 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,15 +16,12 @@
*/ */
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-2020 Florian Schmaus * Copyright © 2015-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -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> { public class MultiMap<K, V> implements TypedCloneable<MultiMap<K, V>> {
/** /**
* The constant value {@value}. * The constant value {@value}.

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Florian Schmaus * Copyright 2015 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,9 +16,19 @@
*/ */
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. /**
public interface Supplier<T> { * An extended version of {@link java.lang.Cloneable}, which defines a generic {@link #clone()}
* 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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlNotSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlNotSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil; import org.jivesoftware.smack.test.util.XmlUnitUtils;
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);
XmlAssertUtil.assertXmlSimilar(expectedXml, actualXml); XmlUnitUtils.assertXmlSimilar(expectedXml, actualXml);
StringBuilder actualXmlTwo = actualXml.toXML(XmlEnvironment.EMPTY); StringBuilder actualXmlTwo = actualXml.toXML(XmlEnvironment.EMPTY);
XmlAssertUtil.assertXmlSimilar(expectedXml, actualXmlTwo); XmlUnitUtils.assertXmlSimilar(expectedXml, actualXmlTwo);
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2014-2020 Florian Schmaus * Copyright 2014-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,7 +26,8 @@ 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;
public class XmlAssertUtil { // TODO: Rename this class to 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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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,9 +962,8 @@ 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(presence); connection.sendStanza(presenceSend.cloneWithNewId());
} }
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,6 +30,7 @@ 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;
@ -43,7 +44,7 @@ import org.jxmpp.util.XmppStringUtils;
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class DiscoverInfo extends IQ implements DiscoverInfoView { public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable<DiscoverInfo> {
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";
@ -302,13 +303,7 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView {
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);
@ -521,7 +516,7 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView {
* 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 { public static final class Feature implements TypedCloneable<Feature> {
private final String variable; private final String variable;
@ -571,6 +566,11 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView {
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-2020 Florian Schmaus * Copyright 2015-2016 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,17 +60,15 @@ 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) {
joinPresenceBuilder = builder.joinPresenceBuilder.ofType(Presence.Type.available); joinPresence = builder.joinPresenceBuilder.ofType(Presence.Type.available).build();
} }
else { else {
joinPresenceBuilder = builder.joinPresence.asBuilder(); joinPresence = builder.joinPresence.clone();
} }
// Indicate the the client supports MUC // Indicate the the client supports MUC
joinPresenceBuilder.addExtension(new MUCInitialPresence(password, maxChars, maxStanzas, seconds, joinPresence.addExtension(new MUCInitialPresence(password, maxChars, maxStanzas, seconds,
since)); since));
joinPresence = joinPresenceBuilder.build();
} }
Presence getJoinPresence(MultiUserChat multiUserChat) { Presence getJoinPresence(MultiUserChat multiUserChat) {
@ -94,8 +92,6 @@ 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,6 +88,7 @@ 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)}.
@ -111,6 +112,9 @@ import org.jxmpp.jid.parts.Resourcepart;
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;
@ -336,8 +340,12 @@ 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 (!multiUserChatManager.providesMucService(mucService)) { if (!KNOWN_MUC_SERVICES.containsKey(mucService)) {
throw new NotAMucServiceException(this); if (multiUserChatManager.providesMucService(mucService)) {
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,7 +64,6 @@ 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.
@ -137,9 +136,6 @@ 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>();
/** /**
@ -396,16 +392,8 @@ 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 {
boolean contains = KNOWN_MUC_SERVICES.containsKey(domainBareJid); return serviceDiscoveryManager.supportsFeature(domainBareJid,
if (!contains) { MUCInitialPresence.NAMESPACE);
if (serviceDiscoveryManager.supportsFeature(domainBareJid,
MUCInitialPresence.NAMESPACE)) {
KNOWN_MUC_SERVICES.put(domainBareJid, null);
return true;
}
}
return contains;
} }
/** /**

View file

@ -32,6 +32,7 @@ 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);
@ -50,8 +51,9 @@ 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, node); super(affiliationsNamespace.type);
items = subList; items = subList;
this.node = node;
} }
public List<Affiliation> getAffiliations() { public List<Affiliation> getAffiliations() {
@ -59,14 +61,19 @@ public class AffiliationsExtension extends NodeExtension {
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
if ((items == null) || (items.size() == 0)) { if ((items == null) || (items.size() == 0)) {
xml.closeEmptyElement(); return super.toXML(enclosingNamespace);
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,8 +16,6 @@
*/ */
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;
/** /**
@ -71,14 +69,26 @@ public class FormNode extends NodeExtension {
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
if (configForm == null) { if (configForm == null) {
xml.closeEmptyElement(); return super.toXML(enclosingNamespace);
return;
} }
else {
StringBuilder builder = new StringBuilder("<");
builder.append(getElementName());
xml.append(configForm); if (getNode() != null) {
xml.closeElement(this); builder.append(" node='");
builder.append(getNode());
builder.append("'>");
}
else
builder.append('>');
builder.append(configForm.toXML());
builder.append("</");
builder.append(getElementName() + '>');
return builder.toString();
}
} }
} }

View file

@ -54,9 +54,13 @@ public class GetItemsRequest extends NodeExtension {
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
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,9 +150,21 @@ public class Item extends NodeExtension {
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
xml.optAttribute("id", getId()); XmlStringBuilder xml = getCommonXml();
xml.closeEmptyElement(); xml.closeEmptyElement();
return xml;
}
protected final XmlStringBuilder getCommonXml() {
XmlStringBuilder xml = new XmlStringBuilder(this);
xml.optAttribute("id", getId());
xml.optAttribute("node", getNode());
return xml;
} }
@Override @Override

View file

@ -20,7 +20,6 @@ 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.
@ -151,21 +150,35 @@ public class ItemsExtension extends NodeExtension implements EmbeddedPacketExten
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
if ((items == null) || (items.size() == 0)) { if ((items == null) || (items.size() == 0)) {
xml.closeEmptyElement(); return super.toXML(enclosingNamespace);
return;
} }
else {
StringBuilder builder = new StringBuilder("<");
builder.append(getElementName());
builder.append(" node='");
builder.append(getNode());
if (notify != null) { if (notify != null) {
xml.attribute(type.getElementAttribute(), notify); builder.append("' ");
xml.rightAngleBracket(); builder.append(type.getElementAttribute());
} else { builder.append("='");
xml.rightAngleBracket(); builder.append(notify.equals(Boolean.TRUE) ? 1 : 0);
xml.append(items); builder.append("'>");
}
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,8 +17,6 @@
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;
@ -80,17 +78,8 @@ public class NodeExtension implements ExtensionElement {
} }
@Override @Override
public final XmlStringBuilder toXML(XmlEnvironment enclosingNamespace) { public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); return '<' + getElementName() + (node == null ? "" : " node='" + node + '\'') + "/>";
xml.optAttribute("node", node);
addXml(xml);
return xml;
}
protected void addXml(XmlStringBuilder xml) {
xml.closeEmptyElement();
} }
@Override @Override

View file

@ -50,15 +50,13 @@ public class OptionsExtension extends NodeExtension {
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
xml.rightAngleBracket(); XmlStringBuilder xml = new XmlStringBuilder();
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();
xml.closeElement(this); return xml;
} }
} }

View file

@ -132,11 +132,14 @@ public class PayloadItem<E extends ExtensionElement> extends Item {
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
xml.optAttribute("id", getId()); XmlStringBuilder xml = getCommonXml();
xml.rightAngleBracket(); xml.rightAngleBracket();
xml.append(payload); xml.append(payload.toXML());
xml.closeElement(this); xml.closeElement(this);
return xml;
} }
@Override @Override

View file

@ -19,8 +19,6 @@ 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.
* *
@ -53,9 +51,18 @@ public class PublishItem<T extends Item> extends NodeExtension {
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public String toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
xml.rightAngleBracket(); StringBuilder builder = new StringBuilder("<");
xml.append(items); builder.append(getElementName());
xml.closeElement(this); builder.append(" node='");
builder.append(getNode());
builder.append("'>");
for (Item item : items) {
builder.append(item.toXML());
}
builder.append("</publish>");
return builder.toString();
} }
} }

View file

@ -16,6 +16,7 @@
*/ */
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;
@ -43,8 +44,11 @@ public class SubscribeExtension extends NodeExtension {
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
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,11 +138,16 @@ public class Subscription extends NodeExtension {
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
xml.attribute("jid", jid); XmlStringBuilder builder = new XmlStringBuilder(this);
xml.optAttribute("subid", id); builder.attribute("jid", jid);
xml.optAttribute("subscription", state);
xml.closeEmptyElement(); builder.optAttribute("node", getNode());
builder.optAttribute("subid", id);
builder.optAttribute("subscription", state.toString());
builder.closeEmptyElement();
return builder;
} }
} }

View file

@ -19,8 +19,6 @@ 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.
* *
@ -93,13 +91,29 @@ public class SubscriptionsExtension extends NodeExtension {
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
if ((items == null) || (items.size() == 0)) { if ((items == null) || (items.size() == 0)) {
xml.closeEmptyElement(); return super.toXML(enclosingNamespace);
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,9 +51,13 @@ public class UnsubscribeExtension extends NodeExtension {
} }
@Override @Override
protected void addXml(XmlStringBuilder xml) { public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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-2020 Florian Schmaus * Copyright 2017 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,14 +16,13 @@
*/ */
package org.jivesoftware.smackx.pubsub; package org.jivesoftware.smackx.pubsub;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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;
@ -41,10 +40,10 @@ public class AffiliationsExtensionTest {
AffiliationsExtension affiliationsExtension = new AffiliationsExtension(affiliationsList, "testNode"); AffiliationsExtension affiliationsExtension = new AffiliationsExtension(affiliationsList, "testNode");
CharSequence xml = affiliationsExtension.toXML(PubSub.NAMESPACE); CharSequence xml = affiliationsExtension.toXML();
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); xml.toString());
} }
} }

View file

@ -16,7 +16,7 @@
*/ */
package org.jivesoftware.smackx.pubsub; package org.jivesoftware.smackx.pubsub;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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; return unavailable.clone();
} }
else { else {
presence = synthesizeUnvailablePresence(jid); presence = synthesizeUnvailablePresence(jid);
@ -1055,7 +1055,7 @@ public final class Roster extends Manager {
} }
} }
else { else {
return presence; return presence.clone();
} }
} }
} }
@ -1084,7 +1084,7 @@ public final class Roster extends Manager {
return presence; return presence;
} }
else { else {
return presence; return presence.clone();
} }
} }
} }
@ -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); res.add(presence.clone());
} }
} }
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); answer.add(presence.clone());
} }
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); res = Arrays.asList(unavailable.clone());
} }
else { else {
Presence presence = synthesizeUnvailablePresence(jid); Presence presence = synthesizeUnvailablePresence(jid);

View file

@ -454,12 +454,10 @@ public class SmackIntegrationTestFramework {
} }
sb.append('\n'); sb.append('\n');
} }
sb.append("Available tests: ").append(numberOfAvailableTests); sb.append("Available tests: ").append(numberOfAvailableTests)
if (!testRunResult.disabledTestClasses.isEmpty() || !testRunResult.disabledTests.isEmpty()) { .append("(#-classes: ").append(testRunResult.disabledTestClasses.size())
sb.append(" (Disabled ").append(testRunResult.disabledTestClasses.size()).append(" classes") .append(", #-tests: ").append(testRunResult.disabledTests.size())
.append(" and ").append(testRunResult.disabledTests.size()).append(" tests"); .append(")\n");
}
sb.append('\n');
LOGGER.info(sb.toString()); LOGGER.info(sb.toString());
for (PreparedTest test : tests) { for (PreparedTest test : tests) {
@ -865,8 +863,6 @@ public class SmackIntegrationTestFramework {
} }
testMethod.invoke(test, connectionsArray); testMethod.invoke(test, connectionsArray);
} }
connectionManager.recycle(connections);
} }
@Override @Override

View file

@ -480,8 +480,6 @@ 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,15 +58,11 @@ public class LoginIntegrationTest extends AbstractSmackLowLevelIntegrationTest {
AbstractXMPPConnection connection = getUnconnectedConnection(); AbstractXMPPConnection connection = getUnconnectedConnection();
connection.connect(); connection.connect();
try { SASLErrorException saslErrorException = assertThrows(SASLErrorException.class,
SASLErrorException saslErrorException = assertThrows(SASLErrorException.class, () -> connection.login(nonExistentUserString, invalidPassword));
() -> connection.login(nonExistentUserString, invalidPassword));
SaslNonza.SASLFailure saslFailure = saslErrorException.getSASLFailure(); SaslNonza.SASLFailure saslFailure = saslErrorException.getSASLFailure();
assertEquals(SASLError.not_authorized, saslFailure.getSASLError()); assertEquals(SASLError.not_authorized, saslFailure.getSASLError());
} finally {
connection.disconnect();
}
} }
} }

View file

@ -38,7 +38,12 @@ public class WaitForClosingStreamElementTest extends AbstractSmackLowLevelIntegr
Field closingStreamReceivedField = AbstractXMPPConnection.class.getDeclaredField("closingStreamReceived"); Field closingStreamReceivedField = AbstractXMPPConnection.class.getDeclaredField("closingStreamReceived");
closingStreamReceivedField.setAccessible(true); closingStreamReceivedField.setAccessible(true);
boolean closingStreamReceived = (boolean) closingStreamReceivedField.get(connection); SynchronizationPoint<?> closingStreamReceived = (SynchronizationPoint<?>) closingStreamReceivedField.get(connection);
assertTrue(closingStreamReceived); Exception failureException = closingStreamReceived.getFailureException();
if (failureException != null) {
throw new AssertionError("Sync poing yielded failure exception", failureException);
}
boolean closingStreamReceivedSuccessful = closingStreamReceived.wasSuccessful();
assertTrue(closingStreamReceivedSuccessful);
} }
} }

View file

@ -20,7 +20,6 @@ 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;
@ -40,7 +39,6 @@ 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;
@ -122,41 +120,4 @@ 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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.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.XmlAssertUtil.assertXmlSimilar; import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Arrays; import java.util.Arrays;

View file

@ -56,6 +56,7 @@ 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 {
@ -70,10 +71,16 @@ 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,9 +26,10 @@ 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;
@ -47,10 +48,7 @@ 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 */
@ -67,32 +65,17 @@ public final class ConnectionAttemptState {
socketChannel = SocketChannel.open(); socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false); socketChannel.configureBlocking(false);
List<Rfc6120TcpRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints; connectionEndpointIterator = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.iterator();
connectionEndpointIterator = endpoints.iterator();
connectionEndpoint = connectionEndpointIterator.next(); connectionEndpoint = connectionEndpointIterator.next();
connectionExceptions = new ArrayList<>(endpoints.size()); connectionExceptions = new ArrayList<>(discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.size());
tcpConnectionEstablishedSyncPoint = new SynchronizationPoint<>(connectionInternal.connection,
"TCP connection establishment");
} }
StateTransitionResult.Failure establishTcpConnection() throws InterruptedException { void establishTcpConnection() {
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(
@ -101,10 +84,8 @@ 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) {
@ -117,9 +98,7 @@ public final class ConnectionAttemptState {
establishingTcpConnectionState, address, true); establishingTcpConnectionState, address, true);
connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent); connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
synchronized (this) { tcpConnectionEstablishedSyncPoint.reportSuccess();
notifyAll();
}
return; return;
} }
@ -145,10 +124,9 @@ public final class ConnectionAttemptState {
establishingTcpConnectionState, address, false); establishingTcpConnectionState, address, false);
connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent); connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
connected = true; // Do not set 'state' here, since this is processed by a reactor thread, which doesn't hold
synchronized (ConnectionAttemptState.this) { // the objects lock.
notifyAll(); tcpConnectionEstablishedSyncPoint.reportSuccess();
}
}); });
} catch (ClosedChannelException e) { } catch (ClosedChannelException e) {
onIOExceptionWhenEstablishingTcpConnection(e, address); onIOExceptionWhenEstablishingTcpConnection(e, address);
@ -159,14 +137,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) {
connectionException = EndpointConnectionException.from( EndpointConnectionException connectionException = EndpointConnectionException.from(
discoveredEndpoints.result.lookupFailures, connectionExceptions); discoveredEndpoints.result.lookupFailures, connectionExceptions);
synchronized (this) { tcpConnectionEstablishedSyncPoint.reportFailure(connectionException);
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,6 +26,11 @@ 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;
@ -39,6 +44,7 @@ import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
@ -59,12 +65,13 @@ 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;
@ -144,6 +151,8 @@ 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
*/ */
@ -157,12 +166,14 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
/** /**
* *
*/ */
private boolean streamFeaturesAfterAuthenticationReceived; private final SynchronizationPoint<XMPPException> maybeCompressFeaturesReceived = new SynchronizationPoint<XMPPException>(
this, "stream compression feature");
/** /**
* *
*/ */
private boolean compressSyncPoint; private final SynchronizationPoint<SmackException> compressSyncPoint = new SynchronizationPoint<>(
this, "stream compression");
/** /**
* The default bundle and defer callback, used for new connections. * The default bundle and defer callback, used for new connections.
@ -186,26 +197,15 @@ 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 #unFailedNonzaExceptionacknowledgedStanzas}. * {@link #unacknowledgedStanzas}.
*/ */
private String smSessionId; private String smSessionId;
/** private final SynchronizationPoint<FailedNonzaException> smResumedSyncPoint = new SynchronizationPoint<>(
* Represents the state of stream management resumption. this, "stream resumed element");
* <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<>(
* Represents the state of stream magement. this, "stream enabled element");
* <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,26 +376,20 @@ 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".
waitForConditionOrThrowConnectionException(() -> streamFeaturesAfterAuthenticationReceived, "compress features from server"); maybeCompressFeaturesReceived.checkIfSuccessOrWait();
// If compression is enabled then request the server to use stream compression. XEP-170 // If compression is enabled then request the server to use stream compression. XEP-170
// recommends to perform stream compression before resource binding. // recommends to perform stream compression before resource binding.
maybeEnableCompression(); maybeEnableCompression();
smResumedSyncPoint = SyncPointState.initial;
smResumptionFailed = null;
if (isSmResumptionPossible()) { if (isSmResumptionPossible()) {
smResumedSyncPoint = SyncPointState.request_sent; smResumedSyncPoint.sendAndWaitForResponse(new Resume(clientHandledStanzasCount, smSessionId));
sendNonza(new Resume(clientHandledStanzasCount, smSessionId)); if (smResumedSyncPoint.wasSuccessful()) {
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;
@ -403,8 +397,7 @@ 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.
assert smResumptionFailed != null; LOGGER.fine("Stream resumption failed, continuing with normal stream establishment process");
LOGGER.fine("Stream resumption failed, continuing with normal stream establishment process: " + smResumptionFailed);
} }
List<Stanza> previouslyUnackedStanzas = new LinkedList<Stanza>(); List<Stanza> previouslyUnackedStanzas = new LinkedList<Stanza>();
@ -425,14 +418,12 @@ 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.
waitForConditionOrThrowConnectionException(() -> smEnabledSyncPoint, "enabling stream mangement"); smEnabledSyncPoint.sendAndWaitForResponseOrThrow(new Enable(useSmResumption, smClientMaxResumptionTime));
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
@ -494,19 +485,14 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
private void shutdown(boolean instant) { private void shutdown(boolean instant) {
// The writer thread may already been finished at this point, for example when the connection is in the // First shutdown the writer, this will result in a closing stream element getting send to
// disconnected-but-resumable state. There is no need to wait for the closing stream tag from the server in this // the server
// case. LOGGER.finer("PacketWriter shutdown()");
if (!packetWriter.done()) { packetWriter.shutdown(instant);
// First shutdown the writer, this will result in a closing stream element getting send to LOGGER.finer("PacketWriter has been shut down");
// 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()");
@ -517,14 +503,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
setWasAuthenticated(); setWasAuthenticated();
try { // Wait for reader and writer threads to be terminated.
boolean readerAndWriterThreadsTermianted = waitForCondition(() -> !packetWriter.running && !packetReader.running); readerWriterSemaphore.acquireUninterruptibly(2);
if (!readerAndWriterThreadsTermianted) { readerWriterSemaphore.release(2);
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;
@ -552,6 +533,15 @@ 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);
@ -662,6 +652,15 @@ 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
@ -689,11 +688,17 @@ 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 SecurityNotPossibleException if TLS is not possible. * @throws CertificateException
* @throws CertificateException if there is an issue with the certificate. * @throws NoSuchAlgorithmException if no such algorithm is available.
* @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 IOException, SecurityNotPossibleException, CertificateException { private void proceedTLSReceived() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, NoSuchProviderException, UnrecoverableKeyException, KeyManagementException, SmackException {
SmackTlsContext smackTlsContext = getSmackTlsContext(); SmackTlsContext smackTlsContext = getSmackTlsContext();
Socket plain = socket; Socket plain = socket;
@ -768,7 +773,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
@Override @Override
public boolean isUsingCompression() { public boolean isUsingCompression() {
return compressionHandler != null && compressSyncPoint; return compressionHandler != null && compressSyncPoint.wasSuccessful();
} }
/** /**
@ -786,10 +791,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, XMPPException { private void maybeEnableCompression() throws SmackException, InterruptedException {
if (!config.isCompressionEnabled()) { if (!config.isCompressionEnabled()) {
return; return;
} }
@ -802,9 +807,7 @@ 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 = false; compressSyncPoint.sendAndWaitForResponseOrThrow(new Compress(compressionHandler.getCompressionMethod()));
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");
} }
@ -832,11 +835,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 true either if TLS was established, or if it was not mandatory. // TLS handled will be successful either if TLS was established, or if it was not mandatory.
waitForConditionOrThrowConnectionException(() -> tlsHandled, "establishing TLS"); tlsHandled.checkIfSuccessOrWaitOrThrow();
// Wait with SASL auth until the SASL mechanisms have been received // Wait with SASL auth until the SASL mechanisms have been received
waitForConditionOrThrowConnectionException(() -> saslFeatureReceived, "SASL mechanisms stream feature from server"); saslFeatureReceived.checkIfSuccessOrWaitOrThrow();
} }
/** /**
@ -854,28 +857,24 @@ 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();
currentSmackException = smackException; tlsHandled.reportFailure(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 = true; tlsHandled.reportSuccess();
notifyWaitingThreads();
} }
} else { } else {
tlsHandled = true; tlsHandled.reportSuccess();
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.
streamFeaturesAfterAuthenticationReceived = true; maybeCompressFeaturesReceived.reportSuccess();
notifyWaitingThreads();
} }
} }
@ -900,8 +899,6 @@ 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.
@ -913,13 +910,11 @@ 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");
running = false; XMPPTCPConnection.this.readerWriterSemaphore.release();
notifyWaitingThreads();
} }
} }
}, threadName); }, threadName);
@ -936,8 +931,10 @@ 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) {
@ -958,17 +955,27 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
break; break;
case "error": case "error":
StreamError streamError = PacketParserUtils.parseStreamError(parser); StreamError streamError = PacketParserUtils.parseStreamError(parser);
// Stream errors are non recoverable, throw this exceptions. Also note that this will set saslFeatureReceived.reportFailure(new StreamErrorException(streamError));
// this exception as current connection exceptions and notify any waiting threads. // Mark the tlsHandled sync point as success, we will use the saslFeatureReceived sync
// 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":
// Secure the connection by negotiating TLS try {
proceedTLSReceived(); // Secure the connection by negotiating TLS
// Send a new opening stream to the server proceedTLSReceived();
openStreamAndResetParser(); // Send a new opening stream to the server
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);
@ -982,8 +989,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
currentSmackException = new SmackException.SmackMessageException("Could not establish compression"); compressSyncPoint.reportFailure(new SmackException.SmackMessageException(
notifyWaitingThreads(); "Could not establish compression"));
break; break;
default: default:
parseAndProcessNonza(parser); parseAndProcessNonza(parser);
@ -997,8 +1004,7 @@ 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 = true; compressSyncPoint.reportSuccess();
notifyWaitingThreads();
break; break;
case Enabled.ELEMENT: case Enabled.ELEMENT:
Enabled enabled = ParseStreamManagement.enabled(parser); Enabled enabled = ParseStreamManagement.enabled(parser);
@ -1006,7 +1012,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");
setCurrentConnectionExceptionAndNotify(xmppException); smEnabledSyncPoint.reportFailure(xmppException);
throw xmppException; throw xmppException;
} }
smServerMaxResumptionTime = enabled.getMaxResumptionTime(); smServerMaxResumptionTime = enabled.getMaxResumptionTime();
@ -1016,19 +1022,28 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
} }
clientHandledStanzasCount = 0; clientHandledStanzasCount = 0;
smWasEnabledAtLeastOnce = true; smWasEnabledAtLeastOnce = true;
smEnabledSyncPoint = true; smEnabledSyncPoint.reportSuccess();
notifyWaitingThreads(); LOGGER.fine("Stream Management (XEP-198): successfully enabled");
break; break;
case Failed.ELEMENT: case Failed.ELEMENT:
Failed failed = ParseStreamManagement.failed(parser); Failed failed = ParseStreamManagement.failed(parser);
if (smResumedSyncPoint == SyncPointState.request_sent) { FailedNonzaException xmppException = new FailedNonzaException(failed, failed.getStanzaErrorCondition());
// This is a <failed/> nonza in a response to resuming a previous stream, failure to do // If only XEP-198 would specify different failure elements for the SM
// so is non-fatal as we can simply continue with resource binding in this case. // enable and SM resume failure case. But this is not the case, so we
smResumptionFailed = failed; // need to determine if this is a 'Failed' response for either 'Enable'
notifyWaitingThreads(); // or 'Resume'.
} else { if (smResumedSyncPoint.requestSent()) {
FailedNonzaException xmppException = new FailedNonzaException(failed, failed.getStanzaErrorCondition()); smResumedSyncPoint.reportFailure(xmppException);
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:
@ -1037,7 +1052,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 = true; smEnabledSyncPoint.reportSuccess();
// 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
@ -1053,8 +1068,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
requestSmAcknowledgementInternal(); requestSmAcknowledgementInternal();
} }
// Mark SM resumption as successful // Mark SM resumption as successful
smResumedSyncPoint = SyncPointState.successful; smResumedSyncPoint.reportSuccess();
notifyWaitingThreads(); LOGGER.fine("Stream Management (XEP-198): Stream resumed");
break; break;
case AckAnswer.ELEMENT: case AckAnswer.ELEMENT:
AckAnswer ackAnswer = ParseStreamManagement.ackAnswer(parser); AckAnswer ackAnswer = ParseStreamManagement.ackAnswer(parser);
@ -1062,7 +1077,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
break; break;
case AckRequest.ELEMENT: case AckRequest.ELEMENT:
ParseStreamManagement.ackRequest(parser); ParseStreamManagement.ackRequest(parser);
if (smEnabledSyncPoint) { if (smEnabledSyncPoint.wasSuccessful()) {
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");
@ -1086,8 +1101,7 @@ 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 = true; closingStreamReceived.reportSuccess();
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
@ -1123,9 +1137,12 @@ 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. // or if the it was caused because the socket got closed. It can not be ignored if it
if (!done) { // happened before (or while) the initial stream opened was send.
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);
@ -1144,6 +1161,12 @@ 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
*/ */
@ -1161,13 +1184,12 @@ 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) {
@ -1182,13 +1204,11 @@ 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");
running = false; XMPPTCPConnection.this.readerWriterSemaphore.release();
notifyWaitingThreads();
} }
} }
}, threadName); }, threadName);
@ -1236,11 +1256,19 @@ 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);
}
}
} }
/** /**
@ -1342,6 +1370,7 @@ 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,
@ -1378,6 +1407,9 @@ 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) {
@ -1689,7 +1721,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; return smEnabledSyncPoint.wasSuccessful();
} }
/** /**
@ -1698,7 +1730,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 == SyncPointState.successful; return smResumedSyncPoint.wasSuccessful();
} }
/** /**

View file

@ -26,7 +26,7 @@ import org.jivesoftware.smack.ConnectionConfiguration;
* </p> * </p>
* <pre> * <pre>
* {@code * {@code
* XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder() * XMPPTCPConnectionConfiguration conf = XMPPConnectionConfiguration.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,13 +47,17 @@ 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.SmackCertificateException; import org.jivesoftware.smack.SmackException.SmackWrappedException;
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; import org.jivesoftware.smack.XMPPException.FailedNonzaException;
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;
@ -587,7 +591,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 discovered endpoints prior performing the lookup. // Assert that there are no stale discovred 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);
@ -740,14 +744,23 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
@Override @Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws InterruptedException, IOException, SmackException, XMPPException { throws InterruptedException, ConnectionUnexpectedTerminatedException, NotConnectedException,
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);
StateTransitionResult.Failure failure = connectionAttemptState.establishTcpConnection(); connectionAttemptState.establishTcpConnection();
if (failure != null) {
return failure; try {
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;
@ -845,7 +858,8 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
@Override @Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws IOException, InterruptedException, SmackException, XMPPException { throws SmackWrappedException, FailedNonzaException, IOException, InterruptedException,
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();
@ -867,7 +881,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
try { try {
tlsState.waitForHandshakeFinished(); tlsState.waitForHandshakeFinished();
} catch (CertificateException e) { } catch (CertificateException e) {
throw new SmackCertificateException(e); throw new SmackWrappedException(e);
} }
connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after TLS established"); connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after TLS established");
@ -1152,20 +1166,43 @@ 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;
connectionInternal.notifyWaitingThreads(); synchronized (this) {
notifyAll();
}
} }
private void onHandshakeFinished() { private void onHandshakeFinished() {
handshakeStatus = TlsHandshakeStatus.successful; handshakeStatus = TlsHandshakeStatus.successful;
connectionInternal.notifyWaitingThreads(); synchronized (this) {
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, SmackException, XMPPException { private void waitForHandshakeFinished() throws InterruptedException, CertificateException, SSLException, ConnectionUnexpectedTerminatedException, NoResponseException {
connectionInternal.waitForCondition(() -> isHandshakeFinished(), "TLS handshake to finish"); final long deadline = System.currentTimeMillis() + connectionInternal.connection.getReplyTimeout();
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;
@ -1198,7 +1235,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
@Override @Override
public void waitUntilInputOutputClosed() throws IOException, CertificateException, InterruptedException, public void waitUntilInputOutputClosed() throws IOException, CertificateException, InterruptedException,
SmackException, XMPPException { ConnectionUnexpectedTerminatedException, NoResponseException {
waitForHandshakeFinished(); waitForHandshakeFinished();
} }

View file

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2014-2020 Florian Schmaus * Copyright 2014-2019 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -124,6 +124,8 @@ 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-alpha5-SNAPSHOT 4.4.0-alpha4-SNAPSHOT