diff --git a/build.gradle b/build.gradle index 96dcab393..0bdce4c74 100644 --- a/build.gradle +++ b/build.gradle @@ -662,6 +662,14 @@ task omemoSignalIntTest { 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 dotGit = new File("$projectDir/.git") if (!dotGit.isDirectory()) return 'non-git build' diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java index 136976a09..6e90e35b2 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -111,6 +111,7 @@ import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smack.util.Predicate; import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.Supplier; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; @@ -174,6 +175,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { SmackConfiguration.getVersion(); } + protected enum SyncPointState { + initial, + request_sent, + successful, + } + /** * A collection of ConnectionListeners which listen for connection closing * and reconnection events. @@ -271,30 +278,29 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { */ protected Writer writer; - protected final SynchronizationPoint tlsHandled = new SynchronizationPoint<>(this, "establishing TLS"); + protected SmackException currentSmackException; + protected XMPPException currentXmppException; + + protected boolean tlsHandled; /** - * Set to success if the last features stanza from the server has been parsed. A XMPP connection + * Set to true 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 * stanza is send by the server. This is set to true once the last feature stanza has been * parsed. */ - protected final SynchronizationPoint lastFeaturesReceived = new SynchronizationPoint<>( - AbstractXMPPConnection.this, "last stream features received from server"); + protected boolean lastFeaturesReceived; /** - * Set to success if the SASL feature has been received. + * Set to true if the SASL feature has been received. */ - protected final SynchronizationPoint saslFeatureReceived = new SynchronizationPoint<>( - AbstractXMPPConnection.this, "SASL mechanisms stream feature from server"); - + protected boolean saslFeatureReceived; /** * A synchronization point which is successful if this connection has received the closing * stream element from the remote end-point, i.e. the server. */ - protected final SynchronizationPoint closingStreamReceived = new SynchronizationPoint<>( - this, "stream closing element received"); + protected boolean closingStreamReceived; /** * The SASLAuthentication manager that is responsible for authenticating with the server. @@ -369,8 +375,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { */ protected boolean wasAuthenticated = false; - protected Exception currentConnectionException; - private final Map setIqRequestHandler = new HashMap<>(); private final Map getIqRequestHandler = new HashMap<>(); @@ -486,10 +490,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { public abstract boolean isUsingCompression(); protected void initState() { - saslFeatureReceived.init(); - lastFeaturesReceived.init(); - tlsHandled.init(); - // TODO: We do not init() closingStreamReceived here, as the integration tests use it to check if we waited for + currentSmackException = null; + currentXmppException = null; + saslFeatureReceived = lastFeaturesReceived = tlsHandled = false; + // TODO: We do not init closingStreamReceived here, as the integration tests use it to check if we waited for // it. } @@ -512,7 +516,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { // Reset the connection state initState(); - closingStreamReceived.init(); + closingStreamReceived = false; streamId = null; try { @@ -657,15 +661,82 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { return streamId; } - protected Resourcepart bindResourceAndEstablishSession(Resourcepart resource) throws XMPPErrorException, - SmackException, InterruptedException { + protected final void throwCurrentConnectionException() throws SmackException, XMPPException { + if (currentSmackException != null) { + throw currentSmackException; + } else if (currentXmppException != null) { + throw currentXmppException; + } + throw new AssertionError("No current connection exception set, although throwCurrentException() was called"); + } + + protected final boolean hasCurrentConnectionException() { + return currentSmackException != null || currentXmppException != null; + } + + protected final void setCurrentConnectionExceptionAndNotify(Exception exception) { + if (exception instanceof SmackException) { + currentSmackException = (SmackException) exception; + } else if (exception instanceof XMPPException) { + currentXmppException = (XMPPException) exception; + } else { + currentSmackException = new SmackException.SmackWrappedException(exception); + } + + notifyWaitingThreads(); + } + + /** + * We use an extra object for {@link #notifyWaitingThreads()} and {@link #waitForCondition(Supplier)}, because all state + * changing methods of the connection are synchronized using the connection instance as monitor. If we now would + * also use the connection instance for the internal process to wait for a condition, the {@link Object#wait()} + * would leave the monitor when it waites, which would allow for another potential call to a state changing function + * to proceed. + */ + private final Object internalMonitor = new Object(); + + protected final void notifyWaitingThreads() { + synchronized (internalMonitor) { + internalMonitor.notifyAll(); + } + } + + protected final boolean waitForCondition(Supplier 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 condition, String waitFor) throws InterruptedException, NoResponseException { + boolean success = waitForCondition(condition); + if (!success) { + throw NoResponseException.newWith(this, waitFor); + } + } + + protected final void waitForConditionOrThrowConnectionException(Supplier 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: // - the servers last features stanza has been parsed // - the timeout occurs LOGGER.finer("Waiting for last features to be received before continuing with resource binding"); - lastFeaturesReceived.checkIfSuccessOrWaitOrThrow(); - + waitForConditionOrThrowConnectionException(() -> lastFeaturesReceived, "last stream features received from server"); if (!hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) { // Server never offered resource binding, which is REQUIRED in XMPP client and @@ -892,6 +963,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { callConnectionClosedListener(); } + private final Object notifyConnectionErrorMonitor = new Object(); + /** * Sends out a notification that there was an error with the connection * and closes the connection. @@ -899,41 +972,31 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { * @param exception the exception that causes the connection close event. */ protected final void notifyConnectionError(final Exception exception) { - if (!isConnected()) { - LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception, - exception); - return; - } + synchronized (notifyConnectionErrorMonitor) { + if (!isConnected()) { + LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception, + exception); + return; + } - ASYNC_BUT_ORDERED.performAsyncButOrdered(this, () -> { - currentConnectionException = exception; + // Note that we first have to set the current connection exception and notify waiting threads, as one of them + // could hold the instance lock, which we also need later when calling instantShutdown(). + setCurrentConnectionExceptionAndNotify(exception); + + // Closes the connection temporary. A if the connection supports stream management, then a reconnection is + // possible. Note that a connection listener of e.g. XMPPTCPConnection will drop the SM state in + // case the Exception is a StreamErrorException. + instantShutdown(); for (StanzaCollector collector : collectors) { 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(() -> { // Notify connection listeners of the error. callConnectionClosedOnErrorListener(exception); }, AbstractXMPPConnection.this + " callConnectionClosedOnErrorListener()"); - }); + } } /** @@ -947,19 +1010,13 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { protected abstract void shutdown(); protected final boolean waitForClosingStreamTagFromServer() { - Exception exception; try { - // After we send the closing stream element, check if there was already a - // closing stream element sent by the server or wait with a timeout for a - // closing stream element to be received from the server. - exception = closingStreamReceived.checkIfSuccessOrWait(); - } catch (InterruptedException | NoResponseException e) { - exception = e; + waitForConditionOrThrowConnectionException(() -> closingStreamReceived, "closing stream tag from the server"); + } catch (InterruptedException | SmackException | XMPPException e) { + LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, e); + return false; } - if (exception != null) { - LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, exception); - } - return exception == null; + return true; } @Override @@ -1817,8 +1874,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { // Only proceed with SASL auth if TLS is disabled or if the server doesn't announce it if (!hasFeature(StartTls.ELEMENT, StartTls.NAMESPACE) || config.getSecurityMode() == SecurityMode.disabled) { - tlsHandled.reportSuccess(); - saslFeatureReceived.reportSuccess(); + tlsHandled = saslFeatureReceived = true; + notifyWaitingThreads(); } } @@ -1827,9 +1884,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { if (hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) { if (!hasFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE) || !config.isCompressionEnabled()) { - // This was was last features from the server is either it did not contain - // compression or if we disabled it - lastFeaturesReceived.reportSuccess(); + // This where the last stream features from the server, either it did not contain + // compression or we disabled it. + lastFeaturesReceived = true; + notifyWaitingThreads(); } } afterFeaturesReceived(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java index f58ed10a4..3d2c0b851 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackException.java @@ -16,6 +16,7 @@ */ package org.jivesoftware.smack; +import java.security.cert.CertificateException; import java.util.Collections; import java.util.List; @@ -371,15 +372,6 @@ public abstract class SmackException extends Exception { } } - public static class ConnectionUnexpectedTerminatedException extends SmackException { - - private static final long serialVersionUID = 1L; - - public ConnectionUnexpectedTerminatedException(Throwable wrappedThrowable) { - super(wrappedThrowable); - } - } - public static class FeatureNotSupportedException extends SmackException { /** @@ -487,4 +479,19 @@ public abstract class SmackException extends 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; + } + } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java index eaff9ca3d..75e1af8e9 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java @@ -17,7 +17,6 @@ package org.jivesoftware.smack; import java.io.IOException; -import java.lang.ref.WeakReference; import java.nio.channels.CancelledKeyException; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectableChannel; @@ -368,13 +367,8 @@ public class SmackReactor { for (SelectionKey selectionKey : selectedKeys) { SelectableChannel channel = selectionKey.channel(); SelectionKeyAttachment selectionKeyAttachment = (SelectionKeyAttachment) selectionKey.attachment(); - ChannelSelectedCallback channelSelectedCallback = selectionKeyAttachment.weaeklyReferencedChannelSelectedCallback.get(); - if (channelSelectedCallback != null) { - channelSelectedCallback.onChannelSelected(channel, selectionKey); - } - else { - selectionKey.cancel(); - } + ChannelSelectedCallback channelSelectedCallback = selectionKeyAttachment.channelSelectedCallback; + channelSelectedCallback.onChannelSelected(channel, selectionKey); } } @@ -422,11 +416,11 @@ public class SmackReactor { } public static final class SelectionKeyAttachment { - private final WeakReference weaeklyReferencedChannelSelectedCallback; + private final ChannelSelectedCallback channelSelectedCallback; private final AtomicBoolean reactorThreadRacing = new AtomicBoolean(); private SelectionKeyAttachment(ChannelSelectedCallback channelSelectedCallback) { - this.weaeklyReferencedChannelSelectedCallback = new WeakReference<>(channelSelectedCallback); + this.channelSelectedCallback = channelSelectedCallback; } private void setRacing() { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SynchronizationPoint.java b/smack-core/src/main/java/org/jivesoftware/smack/SynchronizationPoint.java deleted file mode 100644 index 7652a48e0..000000000 --- a/smack-core/src/main/java/org/jivesoftware/smack/SynchronizationPoint.java +++ /dev/null @@ -1,352 +0,0 @@ -/** - * - * Copyright © 2014-2019 Florian Schmaus - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.jivesoftware.smack; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; - -import org.jivesoftware.smack.SmackException.NoResponseException; -import org.jivesoftware.smack.SmackException.NotConnectedException; -import org.jivesoftware.smack.SmackException.SmackWrappedException; -import org.jivesoftware.smack.packet.Nonza; -import org.jivesoftware.smack.packet.Stanza; -import org.jivesoftware.smack.packet.TopLevelStreamElement; - -public class SynchronizationPoint { - - 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 null 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 null 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. - *

- * The exception is thrown, if state is one of 'Initial', 'NoResponse' or 'RequestSent' - *

- * @return true if synchronization point was successful, false 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, - } -} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/XmppInputOutputFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/XmppInputOutputFilter.java index 18432f564..97fdff246 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/XmppInputOutputFilter.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/XmppInputOutputFilter.java @@ -67,7 +67,8 @@ public interface XmppInputOutputFilter { default void closeInputOutput() { } - default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException, InterruptedException, SmackException { + default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException, + InterruptedException, SmackException, XMPPException { } Object getStats(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java index 061d25e7a..f62caf8ee 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnection.java @@ -35,7 +35,6 @@ import javax.net.ssl.SSLSession; import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.SmackException; -import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackFuture; @@ -78,6 +77,7 @@ import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown; import org.jivesoftware.smack.util.ExtendedAppendable; import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.Supplier; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; @@ -142,7 +142,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne @Override public void onStreamClosed() { - ModularXmppClientToServerConnection.this.closingStreamReceived.reportSuccess(); + ModularXmppClientToServerConnection.this.closingStreamReceived = true; + notifyWaitingThreads(); } @Override @@ -177,7 +178,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne @Override public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException, - ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException { + SmackException, XMPPException { ModularXmppClientToServerConnection.this.newStreamOpenWaitForFeaturesSequence(waitFor); } @@ -198,8 +199,14 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne } @Override - public Exception getCurrentConnectionException() { - return ModularXmppClientToServerConnection.this.currentConnectionException; + public void waitForCondition(Supplier condition, String waitFor) + throws InterruptedException, SmackException, XMPPException { + ModularXmppClientToServerConnection.this.waitForConditionOrThrowConnectionException(condition, waitFor); + } + + @Override + public void notifyWaitingThreads() { + ModularXmppClientToServerConnection.this.notifyWaitingThreads(); } @Override @@ -263,14 +270,13 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne revertedState.resetState(); } - protected void walkStateGraph(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException, - SASLErrorException, FailedNonzaException, IOException, SmackException, InterruptedException { + protected void walkStateGraph(WalkStateGraphContext walkStateGraphContext) + throws XMPPException, IOException, SmackException, InterruptedException { // Save a copy of the current state GraphVertex previousStateVertex = currentStateVertex; try { walkStateGraphInternal(walkStateGraphContext); - } catch (XMPPErrorException | SASLErrorException | FailedNonzaException | IOException | SmackException - | InterruptedException e) { + } catch (IOException | SmackException | InterruptedException | XMPPException e) { currentStateVertex = previousStateVertex; // Unwind the state. State revertedState = currentStateVertex.getElement(); @@ -279,8 +285,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne } } - private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException, - SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException { + private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext) + throws IOException, SmackException, InterruptedException, XMPPException { // Save a copy of the current state final GraphVertex initialStateVertex = currentStateVertex; final State initialState = initialStateVertex.getElement(); @@ -353,21 +359,19 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne } /** - * Attempt to enter a state. Note that this method may return null if this state can be safely ignored ignored. + * Attempt to enter a state. Note that this method may return null if this state can be safely ignored. * * @param successorStateVertex the successor state vertex. * @param walkStateGraphContext the "walk state graph" context. * @return A state transition result or null if this state can be ignored. * @throws SmackException if Smack detected an exceptional situation. - * @throws XMPPErrorException if an XMPP protocol error was received. - * @throws SASLErrorException if a SASL protocol error was returned. + * @throws XMPPException if an XMPP protocol error was received. * @throws IOException if an I/O error occurred. * @throws InterruptedException if the calling thread was interrupted. - * @throws FailedNonzaException if an XMPP protocol failure was received. */ private StateTransitionResult attemptEnterState(GraphVertex successorStateVertex, - WalkStateGraphContext walkStateGraphContext) throws SmackException, XMPPErrorException, - SASLErrorException, IOException, InterruptedException, FailedNonzaException { + WalkStateGraphContext walkStateGraphContext) throws SmackException, XMPPException, + IOException, InterruptedException { final GraphVertex initialStateVertex = currentStateVertex; final State initialState = initialStateVertex.getElement(); final State successorState = successorStateVertex.getElement(); @@ -400,8 +404,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne invokeConnectionStateMachineListener(new ConnectionStateEvent.AboutToTransitionInto(initialState, successorState)); transitionAttemptResult = successorState.transitionInto(walkStateGraphContext); - } catch (SmackException | XMPPErrorException | SASLErrorException | IOException | InterruptedException - | FailedNonzaException e) { + } catch (SmackException | IOException | InterruptedException | XMPPException e) { // 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. unwindState(successorState); @@ -474,8 +477,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne try { walkStateGraph(context); - } catch (XMPPErrorException | SASLErrorException | IOException | SmackException | InterruptedException - | FailedNonzaException e) { + } catch (IOException | SmackException | InterruptedException | XMPPException e) { throw new IllegalStateException("A walk to disconnected state should never throw", e); } } @@ -491,9 +493,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne @Override protected void afterFeaturesReceived() { featuresReceived = true; - synchronized (this) { - notifyAll(); - } + notifyWaitingThreads(); } protected void parseAndProcessElement(String element) { @@ -522,8 +522,10 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne break; case "error": StreamError streamError = PacketParserUtils.parseStreamError(parser, null); - saslFeatureReceived.reportFailure(new StreamErrorException(streamError)); - throw new StreamErrorException(streamError); + StreamErrorException streamErrorException = new StreamErrorException(streamError); + currentXmppException = streamErrorException; + notifyWaitingThreads(); + throw streamErrorException; case "features": parseFeatures(parser); afterFeaturesReceived(); @@ -550,25 +552,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne } protected void waitForFeaturesReceived(String waitFor) - throws InterruptedException, ConnectionUnexpectedTerminatedException, NoResponseException { - 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); - } - } + throws InterruptedException, SmackException, XMPPException { + waitForConditionOrThrowConnectionException(() -> featuresReceived, waitFor); } protected void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException, - ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException { + SmackException, XMPPException { prepareToWaitForFeaturesReceived(); sendStreamOpen(); waitForFeaturesReceived(waitFor); @@ -763,8 +752,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne @Override public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) - throws XMPPErrorException, SASLErrorException, IOException, SmackException, - InterruptedException { + throws IOException, SmackException, InterruptedException, XMPPException { prepareToWaitForFeaturesReceived(); LoginContext loginContext = walkStateGraphContext.getLoginContext(); @@ -813,12 +801,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne @Override public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) - throws XMPPErrorException, SASLErrorException, IOException, SmackException, - InterruptedException { + throws IOException, SmackException, InterruptedException, XMPPException { // 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 // the sync point. - lastFeaturesReceived.reportSuccess(); + lastFeaturesReceived = true; + notifyWaitingThreads(); LoginContext loginContext = walkStateGraphContext.getLoginContext(); Resourcepart resource = bindResourceAndEstablishSession(loginContext.resource); @@ -914,7 +902,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne @Override public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) { - closingStreamReceived.init(); + closingStreamReceived = false; boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(StreamClose.INSTANCE); @@ -936,7 +924,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne XmppInputOutputFilter filter = it.next(); try { filter.waitUntilInputOutputClosed(); - } catch (IOException | CertificateException | InterruptedException | SmackException e) { + } catch (IOException | CertificateException | InterruptedException | SmackException | XMPPException e) { LOGGER.log(Level.WARNING, "waitUntilInputOutputClosed() threw", e); } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java index b08676914..81a485771 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/internal/ModularXmppClientToServerConnectionInternal.java @@ -22,11 +22,12 @@ import java.nio.channels.SelectionKey; import java.util.ListIterator; import java.util.Queue; -import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException; +import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackReactor; import org.jivesoftware.smack.SmackReactor.ChannelSelectedCallback; +import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException.FailedNonzaException; import org.jivesoftware.smack.XmppInputOutputFilter; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection; @@ -38,6 +39,7 @@ import org.jivesoftware.smack.packet.Nonza; import org.jivesoftware.smack.packet.TopLevelStreamElement; import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.util.Consumer; +import org.jivesoftware.smack.util.Supplier; import org.jivesoftware.smack.xml.XmlPullParser; public abstract class ModularXmppClientToServerConnectionInternal { @@ -98,7 +100,7 @@ public abstract class ModularXmppClientToServerConnectionInternal { public abstract ListIterator getXmppInputOutputFilterEndIterator(); public abstract void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException, - ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException; + NoResponseException, NotConnectedException, SmackException, XMPPException; public abstract SmackTlsContext getSmackTlsContext(); @@ -108,7 +110,9 @@ public abstract class ModularXmppClientToServerConnectionInternal { public abstract void asyncGo(Runnable runnable); - public abstract Exception getCurrentConnectionException(); + public abstract void waitForCondition(Supplier condition, String waitFor) throws InterruptedException, SmackException, XMPPException; + + public abstract void notifyWaitingThreads(); public abstract void setCompressionEnabled(boolean compressionEnabled); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Compressed.java b/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Compressed.java index 22026a161..e0278cff4 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Compressed.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Compressed.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2014-2015 Florian Schmaus + * Copyright © 2014-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,15 @@ */ package org.jivesoftware.smack.compress.packet; +import javax.xml.namespace.QName; + import org.jivesoftware.smack.packet.Nonza; public final class Compressed implements Nonza { public static final String ELEMENT = "compressed"; public static final String NAMESPACE = Compress.NAMESPACE; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); public static final Compressed INSTANCE = new Compressed(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Failure.java b/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Failure.java index 473abaccf..2d409db65 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Failure.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/compress/packet/Failure.java @@ -1,6 +1,6 @@ /** * - * Copyright 2018 Florian Schmaus + * Copyright 2018-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ package org.jivesoftware.smack.compress.packet; import java.util.Objects; +import javax.xml.namespace.QName; + import org.jivesoftware.smack.packet.Nonza; import org.jivesoftware.smack.packet.StanzaError; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -26,6 +28,7 @@ public class Failure implements Nonza { public static final String ELEMENT = "failure"; public static final String NAMESPACE = Compress.NAMESPACE; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); public enum CompressFailureError { setup_failed, diff --git a/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java b/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java index 2a018899b..ce0dddeea 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/compression/CompressionModule.java @@ -17,10 +17,8 @@ package org.jivesoftware.smack.compression; import org.jivesoftware.smack.ConnectionConfiguration; -import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException; -import org.jivesoftware.smack.SmackException.NoResponseException; -import org.jivesoftware.smack.SmackException.NotConnectedException; -import org.jivesoftware.smack.XMPPException.FailedNonzaException; +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XmppInputOutputFilter; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedButUnboundStateDescriptor; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ResourceBindingStateDescriptor; @@ -90,8 +88,7 @@ public class CompressionModule extends ModularXmppClientToServerConnectionModule @Override public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) - throws NoResponseException, NotConnectedException, FailedNonzaException, InterruptedException, - ConnectionUnexpectedTerminatedException { + throws InterruptedException, SmackException, XMPPException { final String compressionMethod = selectedCompressionFactory.getCompressionMethod(); connectionInternal.sendAndWaitForResponse(new Compress(compressionMethod), Compressed.class, Failure.class); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java index f8749161d..a4a7b76ea 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/State.java @@ -19,11 +19,9 @@ package org.jivesoftware.smack.fsm; import java.io.IOException; import org.jivesoftware.smack.SmackException; -import org.jivesoftware.smack.XMPPException.FailedNonzaException; -import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; 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 @@ -53,8 +51,7 @@ public abstract class State { } public abstract StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) - throws XMPPErrorException, SASLErrorException, IOException, SmackException, - InterruptedException, FailedNonzaException; + throws IOException, SmackException, InterruptedException, XMPPException; public StateDescriptor getStateDescriptor() { return stateDescriptor; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java index 0ef98885c..aec14b18e 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptor.java @@ -212,7 +212,7 @@ public abstract class StateDescriptor { protected State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) { ModularXmppClientToServerConnection connection = connectionInternal.connection; try { - // If stateClassConstructor is null here, then you probably forgot to override the the + // If stateClassConstructor is null here, then you probably forgot to override the // StateDescriptor.constructState() method? return stateClassConstructor.newInstance(connection, this, connectionInternal); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java index 053d35e77..a9acb4f32 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateTransitionResult.java @@ -67,6 +67,14 @@ public abstract class StateTransitionResult { } } + public static final class FailureCausedByTimeout extends Failure { + + public FailureCausedByTimeout(String failureMessage) { + super(failureMessage); + } + + } + public abstract static class TransitionImpossible extends StateTransitionResult { protected TransitionImpossible(String message) { super(message); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java index 9b2d392a0..a85f7c5b6 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java @@ -17,20 +17,16 @@ package org.jivesoftware.smack.packet; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Locale; -import java.util.Set; import javax.xml.namespace.QName; +import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.util.EqualsUtil; import org.jivesoftware.smack.util.HashCode; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.StringUtils; -import org.jivesoftware.smack.util.TypedCloneable; import org.jivesoftware.smack.util.XmlStringBuilder; import org.jxmpp.jid.Jid; @@ -62,7 +58,7 @@ import org.jxmpp.stringprep.XmppStringprepException; * @author Matt Tucker */ public final class Message extends MessageOrPresence - implements MessageView, TypedCloneable { + implements MessageView { public static final String ELEMENT = "message"; public static final String BODY = "body"; @@ -186,59 +182,6 @@ public final class Message extends MessageOrPresence 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. - *

- * 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 getSubjects() { - List subjectList = getExtensions(Subject.class); - - Set subjects = new HashSet<>(subjectList.size()); - subjects.addAll(subjectList); - - return subjects; - } - /** * Sets the subject of the message. The subject is a short description of * message contents. @@ -267,7 +210,7 @@ public final class Message extends MessageOrPresence @Deprecated // TODO: Remove when stanza builder is ready. public Subject addSubject(String language, String subject) { - language = determineLanguage(language); + language = Stanza.determineLanguage(this, language); List currentSubjects = getExtensions(Subject.class); for (Subject currentSubject : currentSubjects) { @@ -290,7 +233,7 @@ public final class Message extends MessageOrPresence @Deprecated // TODO: Remove when stanza builder is ready. public boolean removeSubject(String language) { - language = determineLanguage(language); + language = Stanza.determineLanguage(this, language); for (Subject subject : getExtensions(Subject.class)) { if (language.equals(subject.language)) { return removeSubject(subject); @@ -311,77 +254,6 @@ public final class Message extends MessageOrPresence 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 getSubjectLanguages() { - Subject defaultSubject = getMessageSubject(null); - List languages = new ArrayList(); - 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. - *

- * 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 getBodies() { - List bodiesList = getExtensions(Body.ELEMENT, Body.NAMESPACE); - Set resultSet = new HashSet<>(bodiesList.size()); - for (ExtensionElement extensionElement : bodiesList) { - Body body = (Body) extensionElement; - resultSet.add(body); - } - return resultSet; - } - /** * Sets the body of the message. * @@ -431,7 +303,7 @@ public final class Message extends MessageOrPresence @Deprecated // TODO: Remove when stanza builder is ready. public Body addBody(String language, String body) { - language = determineLanguage(language); + language = Stanza.determineLanguage(this, language); removeBody(language); @@ -450,7 +322,7 @@ public final class Message extends MessageOrPresence @Deprecated // TODO: Remove when stanza builder is ready. public boolean removeBody(String language) { - language = determineLanguage(language); + language = Stanza.determineLanguage(this, language); for (Body body : getBodies()) { String bodyLanguage = body.getLanguage(); if (Objects.equals(bodyLanguage, language)) { @@ -476,37 +348,6 @@ public final class Message extends MessageOrPresence 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 getBodyLanguages() { - Body defaultBody = getMessageBody(null); - List languages = new ArrayList(); - 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, null will be returned. - * - * @return the thread id of the message, or null 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 * of "chat" messages. @@ -520,18 +361,6 @@ public final class Message extends MessageOrPresence 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 public String getElementName() { return ELEMENT; @@ -542,6 +371,16 @@ public final class Message extends MessageOrPresence 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 public String toString() { StringBuilder sb = new StringBuilder(); @@ -580,7 +419,10 @@ public final class Message extends MessageOrPresence * instance. *

* @return a clone of this message. + * @deprecated use {@link #asBuilder()} instead. */ + // TODO: Remove in Smack 4.5. + @Deprecated @Override public Message clone() { return new Message(this); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageOrPresence.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageOrPresence.java index c885e941c..f48b794a0 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageOrPresence.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageOrPresence.java @@ -1,6 +1,6 @@ /** * - * Copyright 2019 Florian Schmaus + * Copyright 2019-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ */ package org.jivesoftware.smack.packet; +import org.jivesoftware.smack.XMPPConnection; + public abstract class MessageOrPresence> extends Stanza { @Deprecated @@ -33,4 +35,8 @@ public abstract class MessageOrPresence + * 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 getSubjects() { + List subjectList = getExtensions(Subject.class); + + Set 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 getSubjectLanguages() { + Message.Subject defaultSubject = getMessageSubject(null); + List languages = new ArrayList(); + 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. + *

+ * 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 getBodies() { + List bodiesList = getExtensions(Message.Body.QNAME); + Set 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 getBodyLanguages() { + Message.Body defaultBody = getMessageBody(null); + List languages = new ArrayList(); + 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, null will be returned. + * + * @return the thread id of the message, or null if it doesn't exist. + */ + default String getThread() { + Message.Thread thread = getExtension(Message.Thread.class); + if (thread == null) { + return null; + } + return thread.getThread(); + } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/Presence.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/Presence.java index 0f819b429..0c7014110 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/Presence.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/Presence.java @@ -20,9 +20,9 @@ package org.jivesoftware.smack.packet; import java.util.List; import java.util.Locale; +import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.StringUtils; -import org.jivesoftware.smack.util.TypedCloneable; import org.jivesoftware.smack.util.XmlStringBuilder; import org.jxmpp.jid.Jid; @@ -61,7 +61,7 @@ import org.jxmpp.jid.Jid; * @author Matt Tucker */ public final class Presence extends MessageOrPresence - implements PresenceView, TypedCloneable { + implements PresenceView { public static final String ELEMENT = "presence"; @@ -282,6 +282,16 @@ public final class Presence extends MessageOrPresence 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 public String toString() { StringBuilder sb = new StringBuilder(); @@ -343,7 +353,10 @@ public final class Presence extends MessageOrPresence * instance. *

* @return a clone of this presence. + * @deprecated use {@link #asBuilder()} instead. */ + // TODO: Remove in Smack 4.5. + @Deprecated @Override public Presence clone() { return new Presence(this); @@ -354,7 +367,10 @@ public final class Presence extends MessageOrPresence * * @return a "clone" of this presence with a different stanza ID. * @since 4.1.2 + * @deprecated use {@link #asBuilder(XMPPConnection)} or {@link #asBuilder(String)}instead. */ + // TODO: Remove in Smack 4.5. + @Deprecated public Presence cloneWithNewId() { Presence clone = clone(); clone.setNewStanzaId(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/Stanza.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/Stanza.java index ee9377d1a..bce5ba0f5 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/Stanza.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/Stanza.java @@ -575,4 +575,18 @@ public abstract class Stanza implements StanzaView, TopLevelStreamElement { 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 null. + * @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(); + } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/TlsFailure.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/TlsFailure.java index 05aa4b6fa..bce64aa05 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/TlsFailure.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/TlsFailure.java @@ -1,6 +1,6 @@ /** * - * Copyright 2018 Florian Schmaus + * Copyright 2018-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,15 @@ */ package org.jivesoftware.smack.packet; +import javax.xml.namespace.QName; + public final class TlsFailure implements Nonza { public static final TlsFailure INSTANCE = new TlsFailure(); public static final String ELEMENT = "failure"; public static final String NAMESPACE = TlsProceed.NAMESPACE; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); private TlsFailure() { } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/TlsProceed.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/TlsProceed.java index 7967a89b7..cf26f3da1 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/TlsProceed.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/TlsProceed.java @@ -1,6 +1,6 @@ /** * - * Copyright 2018 Florian Schmaus + * Copyright 2018-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,15 @@ */ package org.jivesoftware.smack.packet; +import javax.xml.namespace.QName; + public final class TlsProceed implements Nonza { public static final TlsProceed INSTANCE = new TlsProceed(); public static final String ELEMENT = "proceed"; public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls"; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); private TlsProceed() { } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java b/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java index daa3241a8..f7dcad596 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2015-2019 Florian Schmaus + * Copyright © 2015-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ import java.util.Set; * @param the type of the keys the map uses. * @param the type of the values the map uses. */ -public class MultiMap implements TypedCloneable> { +public class MultiMap { /** * The constant value {@value}. diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/TypedCloneable.java b/smack-core/src/main/java/org/jivesoftware/smack/util/Supplier.java similarity index 61% rename from smack-core/src/main/java/org/jivesoftware/smack/util/TypedCloneable.java rename to smack-core/src/main/java/org/jivesoftware/smack/util/Supplier.java index 0a2121845..f0910adb7 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/TypedCloneable.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/Supplier.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015 Florian Schmaus + * Copyright 2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,9 @@ */ package org.jivesoftware.smack.util; -/** - * An extended version of {@link java.lang.Cloneable}, which defines a generic {@link #clone()} - * method. - * - * @param the type returned by {@link #clone()}. - */ -public interface TypedCloneable extends Cloneable { +// TODO: Replace with java.util.function.Supplier once Smack's minimum Android SDK level is 24 or higher. +public interface Supplier { - /** - * Clone this instance. - * - * @return a cloned version of this instance. - */ - T clone(); + T get(); } diff --git a/smack-core/src/test/java/org/jivesoftware/smack/compress/packet/FailureTest.java b/smack-core/src/test/java/org/jivesoftware/smack/compress/packet/FailureTest.java index cbc5ba88f..9e7ab3b1f 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/compress/packet/FailureTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/compress/packet/FailureTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smack.compress.packet; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import java.io.IOException; diff --git a/smack-core/src/test/java/org/jivesoftware/smack/packet/MessageTest.java b/smack-core/src/test/java/org/jivesoftware/smack/packet/MessageTest.java index 68d8535fa..4c45be4fe 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/packet/MessageTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/packet/MessageTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smack.packet; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/smack-core/src/test/java/org/jivesoftware/smack/packet/PresenceTest.java b/smack-core/src/test/java/org/jivesoftware/smack/packet/PresenceTest.java index 863e4b0ff..19bee63ec 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/packet/PresenceTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/packet/PresenceTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smack.packet; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; diff --git a/smack-core/src/test/java/org/jivesoftware/smack/provider/SaslProviderTest.java b/smack-core/src/test/java/org/jivesoftware/smack/provider/SaslProviderTest.java index 9252625ed..944a0503a 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/provider/SaslProviderTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/provider/SaslProviderTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smack.provider; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import java.io.IOException; diff --git a/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java b/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java index f775a4fcc..3ac5215d5 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java @@ -16,8 +16,8 @@ */ package org.jivesoftware.smack.util; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlNotSimilar; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlNotSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; diff --git a/smack-core/src/test/java/org/jivesoftware/smack/util/XmlStringBuilderTest.java b/smack-core/src/test/java/org/jivesoftware/smack/util/XmlStringBuilderTest.java index 8436a4dd2..425bdf9b3 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/util/XmlStringBuilderTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/util/XmlStringBuilderTest.java @@ -18,7 +18,7 @@ package org.jivesoftware.smack.util; import org.jivesoftware.smack.packet.StandardExtensionElement; import org.jivesoftware.smack.packet.XmlEnvironment; -import org.jivesoftware.smack.test.util.XmlUnitUtils; +import org.jivesoftware.smack.test.util.XmlAssertUtil; import org.junit.jupiter.api.Test; @@ -38,10 +38,10 @@ public class XmlStringBuilderTest { String expectedXml = ""; XmlStringBuilder actualXml = outer.toXML(XmlEnvironment.EMPTY); - XmlUnitUtils.assertXmlSimilar(expectedXml, actualXml); + XmlAssertUtil.assertXmlSimilar(expectedXml, actualXml); StringBuilder actualXmlTwo = actualXml.toXML(XmlEnvironment.EMPTY); - XmlUnitUtils.assertXmlSimilar(expectedXml, actualXmlTwo); + XmlAssertUtil.assertXmlSimilar(expectedXml, actualXmlTwo); } } diff --git a/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/XmlUnitUtils.java b/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/XmlAssertUtil.java similarity index 94% rename from smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/XmlUnitUtils.java rename to smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/XmlAssertUtil.java index 3d9b5ba0f..185bf4eea 100644 --- a/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/XmlUnitUtils.java +++ b/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/XmlAssertUtil.java @@ -1,6 +1,6 @@ /** * - * Copyright 2014-2019 Florian Schmaus + * Copyright 2014-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,7 @@ import org.xmlunit.diff.DefaultNodeMatcher; import org.xmlunit.diff.ElementSelectors; import org.xmlunit.input.NormalizedSource; -// TODO: Rename this class to XmlAssertUtil -public class XmlUnitUtils { +public class XmlAssertUtil { public static void assertXmlNotSimilar(CharSequence xmlOne, CharSequence xmlTwo) { normalizedCompare(xmlOne, xmlTwo).areNotSimilar(); diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotCreateTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotCreateTest.java index 3dc287098..26d940e39 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotCreateTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotCreateTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.httpfileupload; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotRequestCreateTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotRequestCreateTest.java index e30a60c99..3bf92210e 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotRequestCreateTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/SlotRequestCreateTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.httpfileupload; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/provider/SlotProviderTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/provider/SlotProviderTest.java index 17c85f32d..9aa9699d9 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/provider/SlotProviderTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/provider/SlotProviderTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.httpfileupload.provider; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import java.net.MalformedURLException; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java index 87000a6e8..f973fa719 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.mam; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import org.jivesoftware.smack.packet.StreamOpen; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/message_fastening/MessageFasteningElementsTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/message_fastening/MessageFasteningElementsTest.java index 7a36752da..8230f974a 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/message_fastening/MessageFasteningElementsTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/message_fastening/MessageFasteningElementsTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.message_fastening; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/message_markup/MessageMarkupTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/message_markup/MessageMarkupTest.java index 42275642c..63937453e 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/message_markup/MessageMarkupTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/message_markup/MessageMarkupTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.message_markup; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/message_retraction/element/RetractElementTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/message_retraction/element/RetractElementTest.java index 3cb8491de..1278caed4 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/message_retraction/element/RetractElementTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/message_retraction/element/RetractElementTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.message_retraction.element; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.IOException; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/message_retraction/element/RetractedElementTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/message_retraction/element/RetractedElementTest.java index 20dcbd650..ba5acea49 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/message_retraction/element/RetractedElementTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/message_retraction/element/RetractedElementTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.message_retraction.element; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.IOException; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/reference/ReferenceTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/reference/ReferenceTest.java index 84106e0a6..462c80acc 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/reference/ReferenceTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/reference/ReferenceTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.reference; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdTest.java index a95e5caac..a4ccdc6ce 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.sid; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java index 165c4be40..5a96059c7 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.spoiler; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java index 61103d2ee..312b559b5 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java @@ -962,8 +962,9 @@ public final class ServiceDiscoveryManager extends Manager { // to respect ConnectionConfiguration.isSendPresence() final Presence presenceSend = this.presenceSend; if (connection.isAuthenticated() && presenceSend != null) { + Presence presence = presenceSend.asBuilder(connection).build(); try { - connection.sendStanza(presenceSend.cloneWithNewId()); + connection.sendStanza(presence); } catch (InterruptedException | NotConnectedException e) { LOGGER.log(Level.WARNING, "Could could not update presence with caps info", e); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java index 7de48f1be..b4d3f7554 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java @@ -30,7 +30,6 @@ import org.jivesoftware.smack.packet.IqData; import org.jivesoftware.smack.util.EqualsUtil; import org.jivesoftware.smack.util.HashCode; import org.jivesoftware.smack.util.StringUtils; -import org.jivesoftware.smack.util.TypedCloneable; import org.jivesoftware.smack.util.XmlStringBuilder; import org.jxmpp.util.XmppStringUtils; @@ -44,7 +43,7 @@ import org.jxmpp.util.XmppStringUtils; * * @author Gaston Dombiak */ -public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable { +public class DiscoverInfo extends IQ implements DiscoverInfoView { public static final String ELEMENT = QUERY_ELEMENT; public static final String NAMESPACE = "http://jabber.org/protocol/disco#info"; @@ -303,7 +302,13 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable return new DiscoverInfoBuilder(this, stanzaId); } - // TODO: Deprecate in favor of asBuilder(). + /** + * Deprecated, do not use. + * + * @deprecated use {@link #asBuilder(String)} instead. + */ + // TODO: Remove in Smack 4.5. + @Deprecated @Override public DiscoverInfo clone() { return new DiscoverInfo(this); @@ -516,7 +521,7 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable * as well as specific feature types of interest, if any (e.g., for the purpose of feature * negotiation). */ - public static final class Feature implements TypedCloneable { + public static final class Feature { private final String variable; @@ -566,11 +571,6 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable return variable.hashCode(); } - @Override - public Feature clone() { - return new Feature(this); - } - @Override public String toString() { return toXML().toString(); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucEnterConfiguration.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucEnterConfiguration.java index de2b6606e..8a6b4b2fd 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucEnterConfiguration.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucEnterConfiguration.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015-2016 Florian Schmaus + * Copyright 2015-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,15 +60,17 @@ public final class MucEnterConfiguration { since = builder.since; timeout = builder.timeout; + final PresenceBuilder joinPresenceBuilder; if (builder.joinPresence == null) { - joinPresence = builder.joinPresenceBuilder.ofType(Presence.Type.available).build(); + joinPresenceBuilder = builder.joinPresenceBuilder.ofType(Presence.Type.available); } else { - joinPresence = builder.joinPresence.clone(); + joinPresenceBuilder = builder.joinPresence.asBuilder(); } // Indicate the the client supports MUC - joinPresence.addExtension(new MUCInitialPresence(password, maxChars, maxStanzas, seconds, + joinPresenceBuilder.addExtension(new MUCInitialPresence(password, maxChars, maxStanzas, seconds, since)); + joinPresence = joinPresenceBuilder.build(); } Presence getJoinPresence(MultiUserChat multiUserChat) { @@ -92,6 +94,8 @@ public final class MucEnterConfiguration { private long timeout; private final PresenceBuilder joinPresenceBuilder; + + // TODO: Remove in Smack 4.5. private Presence joinPresence; Builder(Resourcepart nickname, XMPPConnection connection) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index 40c3a85a2..f5ee3ec53 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -88,7 +88,6 @@ import org.jxmpp.jid.EntityJid; import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Resourcepart; -import org.jxmpp.util.cache.ExpirationCache; /** * A MultiUserChat room (XEP-45), created with {@link MultiUserChatManager#getMultiUserChat(EntityBareJid)}. @@ -112,9 +111,6 @@ import org.jxmpp.util.cache.ExpirationCache; public class MultiUserChat { private static final Logger LOGGER = Logger.getLogger(MultiUserChat.class.getName()); - private static final ExpirationCache KNOWN_MUC_SERVICES = new ExpirationCache<>( - 100, 1000 * 60 * 60 * 24); - private final XMPPConnection connection; private final EntityBareJid room; private final MultiUserChatManager multiUserChatManager; @@ -340,12 +336,8 @@ public class MultiUserChat { private Presence enter(MucEnterConfiguration conf) throws NotConnectedException, NoResponseException, XMPPErrorException, InterruptedException, NotAMucServiceException { final DomainBareJid mucService = room.asDomainBareJid(); - if (!KNOWN_MUC_SERVICES.containsKey(mucService)) { - if (multiUserChatManager.providesMucService(mucService)) { - KNOWN_MUC_SERVICES.put(mucService, null); - } else { - throw new NotAMucServiceException(this); - } + if (!multiUserChatManager.providesMucService(mucService)) { + throw new NotAMucServiceException(this); } // We enter a room by sending a presence packet where the "to" // field is in the form "roomName@service/nickname" diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java index e2654de89..7d92e9817 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatManager.java @@ -64,6 +64,7 @@ import org.jxmpp.jid.EntityFullJid; import org.jxmpp.jid.EntityJid; import org.jxmpp.jid.Jid; import org.jxmpp.jid.parts.Resourcepart; +import org.jxmpp.util.cache.ExpirationCache; /** * A manager for Multi-User Chat rooms. @@ -136,6 +137,9 @@ public final class MultiUserChatManager extends Manager { private static final StanzaFilter INVITATION_FILTER = new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(new MUCUser()), new NotFilter(MessageTypeFilter.ERROR)); + private static final ExpirationCache KNOWN_MUC_SERVICES = new ExpirationCache<>( + 100, 1000 * 60 * 60 * 24); + private final Set invitationsListeners = new CopyOnWriteArraySet(); /** @@ -392,8 +396,16 @@ public final class MultiUserChatManager extends Manager { */ public boolean providesMucService(DomainBareJid domainBareJid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - return serviceDiscoveryManager.supportsFeature(domainBareJid, - MUCInitialPresence.NAMESPACE); + boolean contains = KNOWN_MUC_SERVICES.containsKey(domainBareJid); + if (!contains) { + if (serviceDiscoveryManager.supportsFeature(domainBareJid, + MUCInitialPresence.NAMESPACE)) { + KNOWN_MUC_SERVICES.put(domainBareJid, null); + return true; + } + } + + return contains; } /** diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java index 2e30dd04c..c18a68243 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java @@ -32,7 +32,6 @@ import org.jivesoftware.smackx.pubsub.Affiliation.AffiliationNamespace; */ public class AffiliationsExtension extends NodeExtension { protected List items = Collections.emptyList(); - private final String node; public AffiliationsExtension() { this(null); @@ -51,9 +50,8 @@ public class AffiliationsExtension extends NodeExtension { } public AffiliationsExtension(AffiliationNamespace affiliationsNamespace, List subList, String node) { - super(affiliationsNamespace.type); + super(affiliationsNamespace.type, node); items = subList; - this.node = node; } public List getAffiliations() { @@ -61,19 +59,14 @@ public class AffiliationsExtension extends NodeExtension { } @Override - public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { + protected void addXml(XmlStringBuilder xml) { if ((items == null) || (items.size() == 0)) { - return super.toXML(enclosingNamespace); - } - 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.closeEmptyElement(); + return; } + + xml.rightAngleBracket(); + xml.append(items); + xml.closeElement(this); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java index 6c3c69171..4d1a46cdd 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java @@ -16,6 +16,8 @@ */ package org.jivesoftware.smackx.pubsub; +import org.jivesoftware.smack.util.XmlStringBuilder; + import org.jivesoftware.smackx.xdata.packet.DataForm; /** @@ -69,26 +71,14 @@ public class FormNode extends NodeExtension { } @Override - public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { + protected void addXml(XmlStringBuilder xml) { if (configForm == null) { - return super.toXML(enclosingNamespace); + xml.closeEmptyElement(); + return; } - else { - StringBuilder builder = new StringBuilder("<"); - builder.append(getElementName()); - if (getNode() != null) { - builder.append(" node='"); - builder.append(getNode()); - builder.append("'>"); - } - else - builder.append('>'); - builder.append(configForm.toXML()); - builder.append("'); - return builder.toString(); - } + xml.append(configForm); + xml.closeElement(this); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/GetItemsRequest.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/GetItemsRequest.java index d55637a7c..f929dd355 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/GetItemsRequest.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/GetItemsRequest.java @@ -54,13 +54,9 @@ public class GetItemsRequest extends NodeExtension { } @Override - public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = new XmlStringBuilder(); - xml.halfOpenElement(getElementName()); - xml.attribute("node", getNode()); + protected void addXml(XmlStringBuilder xml) { xml.optAttribute("subid", getSubscriptionId()); xml.optIntAttribute("max_items", getMaxItems()); xml.closeEmptyElement(); - return xml; } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Item.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Item.java index 727de6c48..0cb4db2e3 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Item.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Item.java @@ -150,21 +150,9 @@ public class Item extends NodeExtension { } @Override - public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = getCommonXml(); - - xml.closeEmptyElement(); - - return xml; - } - - protected final XmlStringBuilder getCommonXml() { - XmlStringBuilder xml = new XmlStringBuilder(this); - + protected void addXml(XmlStringBuilder xml) { xml.optAttribute("id", getId()); - xml.optAttribute("node", getNode()); - - return xml; + xml.closeEmptyElement(); } @Override diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemsExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemsExtension.java index 000138897..02ae67901 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemsExtension.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemsExtension.java @@ -20,6 +20,7 @@ import java.util.List; import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.util.XmlStringBuilder; /** * This class is used for multiple purposes. @@ -150,35 +151,21 @@ public class ItemsExtension extends NodeExtension implements EmbeddedPacketExten } @Override - public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { + protected void addXml(XmlStringBuilder xml) { if ((items == null) || (items.size() == 0)) { - return super.toXML(enclosingNamespace); + xml.closeEmptyElement(); + return; } - else { - StringBuilder builder = new StringBuilder("<"); - builder.append(getElementName()); - builder.append(" node='"); - builder.append(getNode()); - if (notify != null) { - builder.append("' "); - builder.append(type.getElementAttribute()); - builder.append("='"); - builder.append(notify.equals(Boolean.TRUE) ? 1 : 0); - builder.append("'>"); - } - else { - builder.append("'>"); - for (NamedElement item : items) { - builder.append(item.toXML()); - } - } - - builder.append("'); - return builder.toString(); + if (notify != null) { + xml.attribute(type.getElementAttribute(), notify); + xml.rightAngleBracket(); + } else { + xml.rightAngleBracket(); + xml.append(items); } + + xml.closeElement(this); } @Override diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/NodeExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/NodeExtension.java index cc8fac480..d89e438ca 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/NodeExtension.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/NodeExtension.java @@ -17,6 +17,8 @@ package org.jivesoftware.smackx.pubsub; 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; @@ -78,8 +80,17 @@ public class NodeExtension implements ExtensionElement { } @Override - public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - return '<' + getElementName() + (node == null ? "" : " node='" + node + '\'') + "/>"; + public final XmlStringBuilder toXML(XmlEnvironment enclosingNamespace) { + XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); + xml.optAttribute("node", node); + + addXml(xml); + + return xml; + } + + protected void addXml(XmlStringBuilder xml) { + xml.closeEmptyElement(); } @Override diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/OptionsExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/OptionsExtension.java index c9bbf6ab2..7249f9eea 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/OptionsExtension.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/OptionsExtension.java @@ -50,13 +50,15 @@ public class OptionsExtension extends NodeExtension { } @Override - public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = new XmlStringBuilder(); + protected void addXml(XmlStringBuilder xml) { + xml.rightAngleBracket(); + xml.halfOpenElement(getElementName()); xml.attribute("jid", jid); xml.optAttribute("node", getNode()); xml.optAttribute("subid", id); + xml.closeEmptyElement(); - return xml; + xml.closeElement(this); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PayloadItem.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PayloadItem.java index f22a8e876..6cb4210d8 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PayloadItem.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PayloadItem.java @@ -132,14 +132,11 @@ public class PayloadItem extends Item { } @Override - public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = getCommonXml(); - + protected void addXml(XmlStringBuilder xml) { + xml.optAttribute("id", getId()); xml.rightAngleBracket(); - xml.append(payload.toXML()); + xml.append(payload); xml.closeElement(this); - - return xml; } @Override diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PublishItem.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PublishItem.java index ec38e7aa5..7bdead52f 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PublishItem.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PublishItem.java @@ -19,6 +19,8 @@ package org.jivesoftware.smackx.pubsub; import java.util.ArrayList; import java.util.Collection; +import org.jivesoftware.smack.util.XmlStringBuilder; + /** * Represents a request to publish an item(s) to a specific node. * @@ -51,18 +53,9 @@ public class PublishItem extends NodeExtension { } @Override - public String toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - StringBuilder builder = new StringBuilder("<"); - builder.append(getElementName()); - builder.append(" node='"); - builder.append(getNode()); - builder.append("'>"); - - for (Item item : items) { - builder.append(item.toXML()); - } - builder.append(""); - - return builder.toString(); + protected void addXml(XmlStringBuilder xml) { + xml.rightAngleBracket(); + xml.append(items); + xml.closeElement(this); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeExtension.java index 9c4a55b10..64ef67d62 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeExtension.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeExtension.java @@ -16,7 +16,6 @@ */ package org.jivesoftware.smackx.pubsub; -import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.util.XmlStringBuilder; import org.jxmpp.jid.Jid; @@ -44,11 +43,8 @@ public class SubscribeExtension extends NodeExtension { } @Override - public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { - XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); - xml.optAttribute("node", getNode()); + protected void addXml(XmlStringBuilder xml) { xml.attribute("jid", getJid()); xml.closeEmptyElement(); - return xml; } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Subscription.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Subscription.java index 3b81ca016..c9b59896d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Subscription.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Subscription.java @@ -138,16 +138,11 @@ public class Subscription extends NodeExtension { } @Override - public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder builder = new XmlStringBuilder(this); - builder.attribute("jid", jid); - - builder.optAttribute("node", getNode()); - builder.optAttribute("subid", id); - builder.optAttribute("subscription", state.toString()); - - builder.closeEmptyElement(); - return builder; + protected void addXml(XmlStringBuilder xml) { + xml.attribute("jid", jid); + xml.optAttribute("subid", id); + xml.optAttribute("subscription", state); + xml.closeEmptyElement(); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java index 8abde5836..b9f3ee7df 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java @@ -19,6 +19,8 @@ package org.jivesoftware.smackx.pubsub; import java.util.Collections; import java.util.List; +import org.jivesoftware.smack.util.XmlStringBuilder; + /** * Represents the element holding the list of subscription elements. * @@ -91,29 +93,13 @@ public class SubscriptionsExtension extends NodeExtension { } @Override - public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { + protected void addXml(XmlStringBuilder xml) { if ((items == null) || (items.size() == 0)) { - return super.toXML(enclosingNamespace); - } - 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("'); - return builder.toString(); + xml.closeEmptyElement(); + return; } + xml.rightAngleBracket(); + xml.append(items); + xml.closeElement(this); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java index 107271786..3c801ff21 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java @@ -51,13 +51,9 @@ public class UnsubscribeExtension extends NodeExtension { } @Override - public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = new XmlStringBuilder(); - xml.halfOpenElement(getElementName()); + protected void addXml(XmlStringBuilder xml) { xml.attribute("jid", jid); - xml.optAttribute("node", getNode()); xml.optAttribute("subid", id); xml.closeEmptyElement(); - return xml; } } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/attention/AttentionElementTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/attention/AttentionElementTest.java index dd01b7331..755ca3ba0 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/attention/AttentionElementTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/attention/AttentionElementTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.attention; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import org.jivesoftware.smackx.attention.packet.AttentionExtension; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/CloseTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/CloseTest.java index 8fbb488fc..07dd9920e 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/CloseTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/CloseTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.bytestreams.ibb.packet; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtensionTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtensionTest.java index 3042ae07a..beab45d02 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtensionTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtensionTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.bytestreams.ibb.packet; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataTest.java index aa24c043d..2e0a0cacf 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.bytestreams.ibb.packet; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/OpenTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/OpenTest.java index 8206efbf2..d28cd617f 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/OpenTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/bytestreams/ibb/packet/OpenTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.bytestreams.ibb.packet; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/last_interaction/IdleTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/last_interaction/IdleTest.java index 3a6da6b49..bf8c6ae27 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/last_interaction/IdleTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/last_interaction/IdleTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.last_interaction; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodConcretisationTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodConcretisationTest.java index 4ad647e29..f05bec4a2 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodConcretisationTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodConcretisationTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.mood; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodElementTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodElementTest.java index 31d937be8..c1cb72fd5 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodElementTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodElementTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.mood; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/nick/NickTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/nick/NickTest.java index 61c7b70eb..f4379e034 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/nick/NickTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/nick/NickTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.nick; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/AffiliationsExtensionTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/AffiliationsExtensionTest.java index 5a03d5ed3..8c1706091 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/AffiliationsExtensionTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/AffiliationsExtensionTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2017 Florian Schmaus + * Copyright 2017-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,14 @@ */ package org.jivesoftware.smackx.pubsub; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.jivesoftware.smackx.pubsub.Affiliation.Type; +import org.jivesoftware.smackx.pubsub.packet.PubSub; import org.junit.jupiter.api.Test; import org.jxmpp.jid.BareJid; @@ -40,10 +41,10 @@ public class AffiliationsExtensionTest { AffiliationsExtension affiliationsExtension = new AffiliationsExtension(affiliationsList, "testNode"); - CharSequence xml = affiliationsExtension.toXML(); + CharSequence xml = affiliationsExtension.toXML(PubSub.NAMESPACE); assertXmlSimilar("", - xml.toString()); + xml); } } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/ItemValidationTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/ItemValidationTest.java index 9dafb5334..8ed23eed8 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/ItemValidationTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/ItemValidationTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.pubsub; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/softwareinfo/SoftwareInfoFormTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/softwareinfo/SoftwareInfoFormTest.java index 48613cd02..ffeed72aa 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/softwareinfo/SoftwareInfoFormTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/softwareinfo/SoftwareInfoFormTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.softwareinfo; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/usertune/UserTuneElementTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/usertune/UserTuneElementTest.java index b9bdb1189..59c912eaf 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/usertune/UserTuneElementTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/usertune/UserTuneElementTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.usertune; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import java.io.IOException; import java.net.URI; diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java index 214c67e29..67cd5dd89 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.xdata; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import org.junit.jupiter.api.Test; import org.jxmpp.jid.JidTestUtil; diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java index dd05ac97b..02ee0d7e1 100644 --- a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java +++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java @@ -1047,7 +1047,7 @@ public final class Roster extends Manager { } if (presence == null) { if (unavailable != null) { - return unavailable.clone(); + return unavailable; } else { presence = synthesizeUnvailablePresence(jid); @@ -1055,7 +1055,7 @@ public final class Roster extends Manager { } } else { - return presence.clone(); + return presence; } } } @@ -1084,7 +1084,7 @@ public final class Roster extends Manager { return presence; } else { - return presence.clone(); + return presence; } } } @@ -1107,7 +1107,7 @@ public final class Roster extends Manager { } else { res = new ArrayList<>(userPresences.values().size()); for (Presence presence : userPresences.values()) { - res.add(presence.clone()); + res.add(presence); } } return res; @@ -1156,7 +1156,7 @@ public final class Roster extends Manager { Presence unavailable = null; for (Presence presence : userPresences.values()) { if (presence.isAvailable()) { - answer.add(presence.clone()); + answer.add(presence); } else { unavailable = presence; @@ -1166,7 +1166,7 @@ public final class Roster extends Manager { res = answer; } else if (unavailable != null) { - res = Arrays.asList(unavailable.clone()); + res = Arrays.asList(unavailable); } else { Presence presence = synthesizeUnvailablePresence(jid); diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java index b89402df8..e8d7758f8 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java @@ -454,10 +454,12 @@ public class SmackIntegrationTestFramework { } sb.append('\n'); } - sb.append("Available tests: ").append(numberOfAvailableTests) - .append("(#-classes: ").append(testRunResult.disabledTestClasses.size()) - .append(", #-tests: ").append(testRunResult.disabledTests.size()) - .append(")\n"); + sb.append("Available tests: ").append(numberOfAvailableTests); + if (!testRunResult.disabledTestClasses.isEmpty() || !testRunResult.disabledTests.isEmpty()) { + sb.append(" (Disabled ").append(testRunResult.disabledTestClasses.size()).append(" classes") + .append(" and ").append(testRunResult.disabledTests.size()).append(" tests"); + } + sb.append('\n'); LOGGER.info(sb.toString()); for (PreparedTest test : tests) { @@ -863,6 +865,8 @@ public class SmackIntegrationTestFramework { } testMethod.invoke(test, connectionsArray); } + + connectionManager.recycle(connections); } @Override diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java index 207ab1c02..204562fd4 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/XmppConnectionManager.java @@ -480,6 +480,8 @@ public class XmppConnectionManager { synchronized (connectionPool) { 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 // the test run together with all other dynamically created accounts. diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/LoginIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/LoginIntegrationTest.java index 47c299671..7534c4449 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smack/LoginIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/LoginIntegrationTest.java @@ -58,11 +58,15 @@ public class LoginIntegrationTest extends AbstractSmackLowLevelIntegrationTest { AbstractXMPPConnection connection = getUnconnectedConnection(); connection.connect(); - SASLErrorException saslErrorException = assertThrows(SASLErrorException.class, - () -> connection.login(nonExistentUserString, invalidPassword)); + try { + SASLErrorException saslErrorException = assertThrows(SASLErrorException.class, + () -> connection.login(nonExistentUserString, invalidPassword)); - SaslNonza.SASLFailure saslFailure = saslErrorException.getSASLFailure(); - assertEquals(SASLError.not_authorized, saslFailure.getSASLError()); + SaslNonza.SASLFailure saslFailure = saslErrorException.getSASLFailure(); + assertEquals(SASLError.not_authorized, saslFailure.getSASLError()); + } finally { + connection.disconnect(); + } } } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smack/WaitForClosingStreamElementTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smack/WaitForClosingStreamElementTest.java index 023979d64..2326866c7 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smack/WaitForClosingStreamElementTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smack/WaitForClosingStreamElementTest.java @@ -38,12 +38,7 @@ public class WaitForClosingStreamElementTest extends AbstractSmackLowLevelIntegr Field closingStreamReceivedField = AbstractXMPPConnection.class.getDeclaredField("closingStreamReceived"); closingStreamReceivedField.setAccessible(true); - SynchronizationPoint closingStreamReceived = (SynchronizationPoint) closingStreamReceivedField.get(connection); - Exception failureException = closingStreamReceived.getFailureException(); - if (failureException != null) { - throw new AssertionError("Sync poing yielded failure exception", failureException); - } - boolean closingStreamReceivedSuccessful = closingStreamReceived.wasSuccessful(); - assertTrue(closingStreamReceivedSuccessful); + boolean closingStreamReceived = (boolean) closingStreamReceivedField.get(connection); + assertTrue(closingStreamReceived); } } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java index 7ba0c1d1e..8943e9023 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; +import java.util.concurrent.TimeoutException; import org.jivesoftware.smack.MessageListener; import org.jivesoftware.smack.SmackException.NoResponseException; @@ -39,6 +40,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.TestNotPossibleException; import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; import org.igniterealtime.smack.inttest.util.ResultSyncPoint; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.impl.JidCreate; @@ -120,4 +122,41 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest { mucAsSeenByOne.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); + } } diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java index 2d8d55419..143580531 100644 --- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpElementTest.java @@ -18,7 +18,7 @@ package org.jivesoftware.smackx.ox; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertNotNull; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PubkeyElementTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PubkeyElementTest.java index a67d4870d..46e053891 100644 --- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PubkeyElementTest.java +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PubkeyElementTest.java @@ -17,7 +17,7 @@ package org.jivesoftware.smackx.ox; import static junit.framework.TestCase.assertEquals; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import java.io.IOException; import java.text.ParseException; diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PublicKeysListElementTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PublicKeysListElementTest.java index 54f2f98cc..f90015b9f 100644 --- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PublicKeysListElementTest.java +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PublicKeysListElementTest.java @@ -17,7 +17,7 @@ package org.jivesoftware.smackx.ox; import static junit.framework.TestCase.assertEquals; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import java.util.Date; diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/SecretkeyElementTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/SecretkeyElementTest.java index 56902d75f..b9745bb20 100644 --- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/SecretkeyElementTest.java +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/SecretkeyElementTest.java @@ -17,7 +17,7 @@ package org.jivesoftware.smackx.ox; import static junit.framework.TestCase.assertTrue; -import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar; +import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; import java.nio.charset.Charset; import java.util.Arrays; diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java index fbf33e6a3..e7106820a 100644 --- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java @@ -56,7 +56,6 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.jupiter.api.Test; import org.jxmpp.jid.EntityBareJid; -import org.jxmpp.jid.JidTestUtil; import org.pgpainless.decryption_verification.OpenPgpMetadata; public class OXInstantMessagingManagerTest extends SmackTestSuite { @@ -71,16 +70,10 @@ public class OXInstantMessagingManagerTest extends SmackTestSuite { public void test() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException, SmackException, MissingUserIdOnKeyException, InterruptedException, XMPPException, XmlPullParserException { - DummyConnection aliceCon = new DummyConnection( - DummyConnection.DummyConnectionConfiguration.builder() - .setXmppDomain(JidTestUtil.EXAMPLE_ORG) - .setUsernameAndPassword("alice", "dummypass").build()); + DummyConnection aliceCon = new DummyConnection(); aliceCon.connect().login(); - DummyConnection bobCon = new DummyConnection( - DummyConnection.DummyConnectionConfiguration.builder() - .setXmppDomain(JidTestUtil.EXAMPLE_ORG) - .setUsernameAndPassword("bob", "dummypass").build()); + DummyConnection bobCon = new DummyConnection(); bobCon.connect().login(); FileBasedOpenPgpStore aliceStore = new FileBasedOpenPgpStore(new File(basePath, "alice")); diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/ConnectionAttemptState.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/ConnectionAttemptState.java index 415af19f9..d8a3890e0 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/ConnectionAttemptState.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/ConnectionAttemptState.java @@ -26,10 +26,9 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import org.jivesoftware.smack.SmackException.ConnectionException; import org.jivesoftware.smack.SmackException.EndpointConnectionException; -import org.jivesoftware.smack.SynchronizationPoint; 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.rce.Rfc6120TcpRemoteConnectionEndpoint; import org.jivesoftware.smack.util.Async; @@ -48,7 +47,10 @@ public final class ConnectionAttemptState { final SocketChannel socketChannel; final List> connectionExceptions; - final SynchronizationPoint tcpConnectionEstablishedSyncPoint; + + EndpointConnectionException connectionException; + boolean connected; + long deadline; final Iterator connectionEndpointIterator; /** The current connection endpoint we are trying */ @@ -65,17 +67,32 @@ public final class ConnectionAttemptState { socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); - connectionEndpointIterator = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.iterator(); + List endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints; + connectionEndpointIterator = endpoints.iterator(); connectionEndpoint = connectionEndpointIterator.next(); - connectionExceptions = new ArrayList<>(discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.size()); - - tcpConnectionEstablishedSyncPoint = new SynchronizationPoint<>(connectionInternal.connection, - "TCP connection establishment"); + connectionExceptions = new ArrayList<>(endpoints.size()); } - void establishTcpConnection() { + StateTransitionResult.Failure establishTcpConnection() throws InterruptedException { RemoteConnectionEndpoint.InetSocketAddressCoupling address = nextAddress(); 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(connectionException); } private void establishTcpConnection( @@ -84,8 +101,10 @@ public final class ConnectionAttemptState { establishingTcpConnectionState, address); connectionInternal.invokeConnectionStateMachineListener(connectingToHostEvent); - final boolean connected; 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 { connected = socketChannel.connect(inetSocketAddress); } catch (IOException e) { @@ -98,7 +117,9 @@ public final class ConnectionAttemptState { establishingTcpConnectionState, address, true); connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent); - tcpConnectionEstablishedSyncPoint.reportSuccess(); + synchronized (this) { + notifyAll(); + } return; } @@ -124,9 +145,10 @@ public final class ConnectionAttemptState { establishingTcpConnectionState, address, false); connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent); - // Do not set 'state' here, since this is processed by a reactor thread, which doesn't hold - // the objects lock. - tcpConnectionEstablishedSyncPoint.reportSuccess(); + connected = true; + synchronized (ConnectionAttemptState.this) { + notifyAll(); + } }); } catch (ClosedChannelException e) { onIOExceptionWhenEstablishingTcpConnection(e, address); @@ -137,14 +159,14 @@ public final class ConnectionAttemptState { RemoteConnectionEndpoint.InetSocketAddressCoupling failedAddress) { RemoteConnectionEndpoint.InetSocketAddressCoupling nextInetSocketAddress = nextAddress(); if (nextInetSocketAddress == null) { - EndpointConnectionException connectionException = EndpointConnectionException.from( + connectionException = EndpointConnectionException.from( discoveredEndpoints.result.lookupFailures, connectionExceptions); - tcpConnectionEstablishedSyncPoint.reportFailure(connectionException); + synchronized (this) { + notifyAll(); + } return; } - tcpConnectionEstablishedSyncPoint.resetTimeout(); - RemoteConnectionException rce = new RemoteConnectionException<>( failedAddress, exception); connectionExceptions.add(rce); diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index ea34b9518..ded929681 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -26,11 +26,6 @@ import java.io.Writer; import java.net.InetAddress; import java.net.InetSocketAddress; 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.util.ArrayList; import java.util.Collection; @@ -44,7 +39,6 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; @@ -65,13 +59,12 @@ import org.jivesoftware.smack.SmackException.AlreadyConnectedException; import org.jivesoftware.smack.SmackException.AlreadyLoggedInException; import org.jivesoftware.smack.SmackException.ConnectionException; import org.jivesoftware.smack.SmackException.EndpointConnectionException; -import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotLoggedInException; +import org.jivesoftware.smack.SmackException.SecurityNotPossibleException; import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException; import org.jivesoftware.smack.SmackFuture; import org.jivesoftware.smack.StanzaListener; -import org.jivesoftware.smack.SynchronizationPoint; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException.FailedNonzaException; @@ -151,8 +144,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { private SSLSocket secureSocket; - private final Semaphore readerWriterSemaphore = new Semaphore(2); - /** * Protected access level because of unit test purposes */ @@ -166,14 +157,12 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { /** * */ - private final SynchronizationPoint maybeCompressFeaturesReceived = new SynchronizationPoint( - this, "stream compression feature"); + private boolean streamFeaturesAfterAuthenticationReceived; /** * */ - private final SynchronizationPoint compressSyncPoint = new SynchronizationPoint<>( - this, "stream compression"); + private boolean compressSyncPoint; /** * The default bundle and defer callback, used for new connections. @@ -197,15 +186,26 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { /** * The stream ID of the stream that is currently resumable, ie. the stream we hold the state * for in {@link #clientHandledStanzasCount}, {@link #serverHandledStanzasCount} and - * {@link #unacknowledgedStanzas}. + * {@link #unFailedNonzaExceptionacknowledgedStanzas}. */ private String smSessionId; - private final SynchronizationPoint smResumedSyncPoint = new SynchronizationPoint<>( - this, "stream resumed element"); + /** + * Represents the state of stream management resumption. + *

+ * Unlike other sync points, this sync point is marked volatile because it is also read by the reader thread. + *

+ */ + private volatile SyncPointState smResumedSyncPoint; + private Failed smResumptionFailed; - private final SynchronizationPoint smEnabledSyncPoint = new SynchronizationPoint<>( - this, "stream enabled element"); + /** + * Represents the state of stream magement. + *

+ * This boolean is marked volatile as it is read by various threads, including the reader thread via {@link #isSmEnabled()}. + *

+ */ + private volatile boolean smEnabledSyncPoint; /** * The client's preferred maximum resumption time in seconds. @@ -376,20 +376,26 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { SmackException, IOException, InterruptedException { // Authenticate using SASL SSLSession sslSession = secureSocket != null ? secureSocket.getSession() : null; + + streamFeaturesAfterAuthenticationReceived = false; authenticate(username, password, config.getAuthzid(), sslSession); // Wait for stream features after the authentication. // TODO: The name of this synchronization point "maybeCompressFeaturesReceived" is not perfect. It should be // renamed to "streamFeaturesAfterAuthenticationReceived". - maybeCompressFeaturesReceived.checkIfSuccessOrWait(); + waitForConditionOrThrowConnectionException(() -> streamFeaturesAfterAuthenticationReceived, "compress features from server"); // If compression is enabled then request the server to use stream compression. XEP-170 // recommends to perform stream compression before resource binding. maybeEnableCompression(); + smResumedSyncPoint = SyncPointState.initial; + smResumptionFailed = null; if (isSmResumptionPossible()) { - smResumedSyncPoint.sendAndWaitForResponse(new Resume(clientHandledStanzasCount, smSessionId)); - if (smResumedSyncPoint.wasSuccessful()) { + smResumedSyncPoint = SyncPointState.request_sent; + sendNonza(new Resume(clientHandledStanzasCount, smSessionId)); + waitForCondition(() -> smResumedSyncPoint == SyncPointState.successful || smResumptionFailed != null, "resume previous stream"); + if (smResumedSyncPoint == SyncPointState.successful) { // We successfully resumed the stream, be done here afterSuccessfulLogin(true); return; @@ -397,7 +403,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // SM resumption failed, what Smack does here is to report success of // lastFeaturesReceived in case of sm resumption was answered with 'failed' so that // normal resource binding can be tried. - LOGGER.fine("Stream resumption failed, continuing with normal stream establishment process"); + assert smResumptionFailed != null; + LOGGER.fine("Stream resumption failed, continuing with normal stream establishment process: " + smResumptionFailed); } List previouslyUnackedStanzas = new LinkedList(); @@ -418,12 +425,14 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // unacknowledgedStanzas and become duplicated on reconnect. See SMACK-706. bindResourceAndEstablishSession(resource); + smEnabledSyncPoint = false; if (isSmAvailable() && useSm) { // Remove what is maybe left from previously stream managed sessions serverHandledStanzasCount = 0; + sendNonza(new Enable(useSmResumption, smClientMaxResumptionTime)); // 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. - smEnabledSyncPoint.sendAndWaitForResponseOrThrow(new Enable(useSmResumption, smClientMaxResumptionTime)); + waitForConditionOrThrowConnectionException(() -> smEnabledSyncPoint, "enabling stream mangement"); synchronized (requestAckPredicates) { if (requestAckPredicates.isEmpty()) { // Assure that we have at lest one predicate set up that so that we request acks @@ -485,14 +494,19 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } private void shutdown(boolean instant) { - // First shutdown the writer, this will result in a closing stream element getting send to - // the server - LOGGER.finer("PacketWriter shutdown()"); - packetWriter.shutdown(instant); - LOGGER.finer("PacketWriter has been shut down"); + // The writer thread may already been finished at this point, for example when the connection is in the + // disconnected-but-resumable state. There is no need to wait for the closing stream tag from the server in this + // case. + if (!packetWriter.done()) { + // First shutdown the writer, this will result in a closing stream element getting send to + // the server + LOGGER.finer("PacketWriter shutdown()"); + packetWriter.shutdown(instant); + LOGGER.finer("PacketWriter has been shut down"); - if (!instant) { - waitForClosingStreamTagFromServer(); + if (!instant) { + waitForClosingStreamTagFromServer(); + } } LOGGER.finer("PacketReader shutdown()"); @@ -503,9 +517,14 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { setWasAuthenticated(); - // Wait for reader and writer threads to be terminated. - readerWriterSemaphore.acquireUninterruptibly(2); - readerWriterSemaphore.release(2); + try { + boolean readerAndWriterThreadsTermianted = waitForCondition(() -> !packetWriter.running && !packetReader.running); + if (!readerAndWriterThreadsTermianted) { + LOGGER.severe("Reader and writer threads did not terminate"); + } + } catch (InterruptedException e) { + LOGGER.log(Level.FINE, "Interrupted while waiting for reader and writer threads to terminate", e); + } if (disconnectedButResumeable) { return; @@ -533,15 +552,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { initState(); } - @Override - protected void initState() { - super.initState(); - maybeCompressFeaturesReceived.init(); - compressSyncPoint.init(); - smResumedSyncPoint.init(); - smEnabledSyncPoint.init(); - } - @Override public void sendNonza(Nonza element) throws NotConnectedException, InterruptedException { packetWriter.sendStreamElement(element); @@ -652,15 +662,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // Set the reader and writer instance variables 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 packetWriter.init(); // Start the reader thread. The startup() method will block until we @@ -688,17 +689,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { * existing plain connection and perform a handshake. This method won't return until the * connection has finished the handshake or an error occurred while securing the connection. * @throws IOException if an I/O error occurred. - * @throws CertificateException - * @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. + * @throws SecurityNotPossibleException if TLS is not possible. + * @throws CertificateException if there is an issue with the certificate. */ @SuppressWarnings("LiteralClassName") - private void proceedTLSReceived() throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, NoSuchProviderException, UnrecoverableKeyException, KeyManagementException, SmackException { + private void proceedTLSReceived() throws IOException, SecurityNotPossibleException, CertificateException { SmackTlsContext smackTlsContext = getSmackTlsContext(); Socket plain = socket; @@ -773,7 +768,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { @Override public boolean isUsingCompression() { - return compressionHandler != null && compressSyncPoint.wasSuccessful(); + return compressionHandler != null && compressSyncPoint; } /** @@ -791,10 +786,10 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { * * @throws NotConnectedException if the XMPP connection is not connected. * @throws 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 XMPPException if an XMPP protocol error was received. */ - private void maybeEnableCompression() throws SmackException, InterruptedException { + private void maybeEnableCompression() throws SmackException, InterruptedException, XMPPException { if (!config.isCompressionEnabled()) { return; } @@ -807,7 +802,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // If stream compression was offered by the server and we want to use // compression then send compression request to the server if ((compressionHandler = maybeGetCompressionHandler(compression)) != null) { - compressSyncPoint.sendAndWaitForResponseOrThrow(new Compress(compressionHandler.getCompressionMethod())); + compressSyncPoint = false; + sendNonza(new Compress(compressionHandler.getCompressionMethod())); + waitForConditionOrThrowConnectionException(() -> compressSyncPoint, "establishing stream compression"); } else { LOGGER.warning("Could not enable compression because no matching handler/method pair was found"); } @@ -835,11 +832,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // We connected successfully to the servers TCP port initConnection(); - // TLS handled will be successful either if TLS was established, or if it was not mandatory. - tlsHandled.checkIfSuccessOrWaitOrThrow(); + // TLS handled will be true either if TLS was established, or if it was not mandatory. + waitForConditionOrThrowConnectionException(() -> tlsHandled, "establishing TLS"); // Wait with SASL auth until the SASL mechanisms have been received - saslFeatureReceived.checkIfSuccessOrWaitOrThrow(); + waitForConditionOrThrowConnectionException(() -> saslFeatureReceived, "SASL mechanisms stream feature from server"); } /** @@ -857,24 +854,28 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { if (startTlsFeature != null) { if (startTlsFeature.required() && config.getSecurityMode() == SecurityMode.disabled) { SecurityRequiredByServerException smackException = new SecurityRequiredByServerException(); - tlsHandled.reportFailure(smackException); + currentSmackException = smackException; + notifyWaitingThreads(); throw smackException; } if (config.getSecurityMode() != ConnectionConfiguration.SecurityMode.disabled) { sendNonza(new StartTls()); } else { - tlsHandled.reportSuccess(); + tlsHandled = true; + notifyWaitingThreads(); } } else { - tlsHandled.reportSuccess(); + tlsHandled = true; + notifyWaitingThreads(); } if (isSaslAuthenticated()) { // 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 // from the server. - maybeCompressFeaturesReceived.reportSuccess(); + streamFeaturesAfterAuthenticationReceived = true; + notifyWaitingThreads(); } } @@ -899,6 +900,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { private volatile boolean done; + private boolean running; + /** * Initializes the reader in order to be used. The reader is initialized during the * first connection and when reconnecting due to an abruptly disconnection. @@ -910,11 +913,13 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { @Override public void run() { LOGGER.finer(threadName + " start"); + running = true; try { parsePackets(); } finally { LOGGER.finer(threadName + " exit"); - XMPPTCPConnection.this.readerWriterSemaphore.release(); + running = false; + notifyWaitingThreads(); } } }, threadName); @@ -931,10 +936,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { * Parse top-level packets in order to process them further. */ private void parsePackets() { - boolean initialStreamOpenSend = false; try { openStreamAndResetParser(); - initialStreamOpenSend = true; XmlPullParser.Event eventType = parser.getEventType(); while (!done) { switch (eventType) { @@ -955,27 +958,17 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { break; case "error": StreamError streamError = PacketParserUtils.parseStreamError(parser); - saslFeatureReceived.reportFailure(new StreamErrorException(streamError)); - // 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(); + // Stream errors are non recoverable, throw this exceptions. Also note that this will set + // this exception as current connection exceptions and notify any waiting threads. throw new StreamErrorException(streamError); case "features": parseFeaturesAndNotify(parser); break; case "proceed": - try { - // Secure the connection by negotiating TLS - proceedTLSReceived(); - // Send a new opening stream to the server - openStreamAndResetParser(); - } - catch (Exception e) { - SmackException.SmackWrappedException smackException = new SmackException.SmackWrappedException(e); - tlsHandled.reportFailure(smackException); - throw e; - } + // Secure the connection by negotiating TLS + proceedTLSReceived(); + // Send a new opening stream to the server + openStreamAndResetParser(); break; case "failure": String namespace = parser.getNamespace(null); @@ -989,8 +982,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // situation. It is still possible to authenticate and // use the connection but using an uncompressed connection // TODO Parse failure stanza - compressSyncPoint.reportFailure(new SmackException.SmackMessageException( - "Could not establish compression")); + currentSmackException = new SmackException.SmackMessageException("Could not establish compression"); + notifyWaitingThreads(); break; default: parseAndProcessNonza(parser); @@ -1004,7 +997,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // Send a new opening stream to the server openStreamAndResetParser(); // Notify that compression is being used - compressSyncPoint.reportSuccess(); + compressSyncPoint = true; + notifyWaitingThreads(); break; case Enabled.ELEMENT: Enabled enabled = ParseStreamManagement.enabled(parser); @@ -1012,7 +1006,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { smSessionId = enabled.getId(); if (StringUtils.isNullOrEmpty(smSessionId)) { SmackException xmppException = new SmackException.SmackMessageException("Stream Management 'enabled' element with resume attribute but without session id received"); - smEnabledSyncPoint.reportFailure(xmppException); + setCurrentConnectionExceptionAndNotify(xmppException); throw xmppException; } smServerMaxResumptionTime = enabled.getMaxResumptionTime(); @@ -1022,28 +1016,19 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } clientHandledStanzasCount = 0; smWasEnabledAtLeastOnce = true; - smEnabledSyncPoint.reportSuccess(); - LOGGER.fine("Stream Management (XEP-198): successfully enabled"); + smEnabledSyncPoint = true; + notifyWaitingThreads(); break; case Failed.ELEMENT: Failed failed = ParseStreamManagement.failed(parser); - FailedNonzaException xmppException = new FailedNonzaException(failed, failed.getStanzaErrorCondition()); - // If only XEP-198 would specify different failure elements for the SM - // enable and SM resume failure case. But this is not the case, so we - // need to determine if this is a 'Failed' response for either 'Enable' - // or 'Resume'. - if (smResumedSyncPoint.requestSent()) { - smResumedSyncPoint.reportFailure(xmppException); - } - else { - if (!smEnabledSyncPoint.requestSent()) { - throw new IllegalStateException("Failed element received but SM was not previously enabled"); - } - smEnabledSyncPoint.reportFailure(new SmackException.SmackWrappedException(xmppException)); - // Report success for last lastFeaturesReceived so that in case a - // failed resumption, we can continue with normal resource binding. - // See text of XEP-198 5. below Example 11. - lastFeaturesReceived.reportSuccess(); + if (smResumedSyncPoint == SyncPointState.request_sent) { + // This is a nonza in a response to resuming a previous stream, failure to do + // so is non-fatal as we can simply continue with resource binding in this case. + smResumptionFailed = failed; + notifyWaitingThreads(); + } else { + FailedNonzaException xmppException = new FailedNonzaException(failed, failed.getStanzaErrorCondition()); + setCurrentConnectionExceptionAndNotify(xmppException); } break; case Resumed.ELEMENT: @@ -1052,7 +1037,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { throw new StreamIdDoesNotMatchException(smSessionId, resumed.getPrevId()); } // Mark SM as enabled - smEnabledSyncPoint.reportSuccess(); + smEnabledSyncPoint = true; // First, drop the stanzas already handled by the server processHandledCount(resumed.getHandledCount()); // Then re-send what is left in the unacknowledged queue @@ -1068,8 +1053,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { requestSmAcknowledgementInternal(); } // Mark SM resumption as successful - smResumedSyncPoint.reportSuccess(); - LOGGER.fine("Stream Management (XEP-198): Stream resumed"); + smResumedSyncPoint = SyncPointState.successful; + notifyWaitingThreads(); break; case AckAnswer.ELEMENT: AckAnswer ackAnswer = ParseStreamManagement.ackAnswer(parser); @@ -1077,7 +1062,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { break; case AckRequest.ELEMENT: ParseStreamManagement.ackRequest(parser); - if (smEnabledSyncPoint.wasSuccessful()) { + if (smEnabledSyncPoint) { sendSmAcknowledgementInternal(); } else { LOGGER.warning("SM Ack Request received while SM is not enabled"); @@ -1101,7 +1086,8 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { // did re-start the queue again, causing this writer to assume that the queue is not // shutdown, which results in a call to disconnect(). final boolean queueWasShutdown = packetWriter.queue.isShutdown(); - closingStreamReceived.reportSuccess(); + closingStreamReceived = true; + notifyWaitingThreads(); if (queueWasShutdown) { // We received a closing stream element *after* we initiated the @@ -1137,12 +1123,9 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } } 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' - // or if the it was caused because the socket got closed. It can not be ignored if it - // happened before (or while) the initial stream opened was send. - if (!(done || packetWriter.queue.isShutdown()) || !initialStreamOpenSend) { + // or if the it was caused because the socket got closed. + if (!done) { // Close the connection and notify connection listeners of the // error. notifyConnectionError(e); @@ -1161,12 +1144,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { private final ArrayBlockingQueueWithShutdown queue = new ArrayBlockingQueueWithShutdown<>( QUEUE_SIZE, true); - /** - * Needs to be protected for unit testing purposes. - */ - protected SynchronizationPoint shutdownDone = new SynchronizationPoint<>( - XMPPTCPConnection.this, "shutdown completed"); - /** * If set, the stanza writer is shut down */ @@ -1184,12 +1161,13 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { */ private boolean shouldBundleAndDefer; + private boolean running; + /** * 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. */ void init() { - shutdownDone.init(); shutdownTimestamp = null; if (unacknowledgedStanzas != null) { @@ -1204,11 +1182,13 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { @Override public void run() { LOGGER.finer(threadName + " start"); + running = true; try { writePackets(); } finally { LOGGER.finer(threadName + " exit"); - XMPPTCPConnection.this.readerWriterSemaphore.release(); + running = false; + notifyWaitingThreads(); } } }, threadName); @@ -1256,19 +1236,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { /** * Shuts down the stanza writer. Once this method has been called, no further * packets will be written to the server. - * @throws InterruptedException if the calling thread was interrupted. */ void shutdown(boolean instant) { instantShutdown = instant; queue.shutdown(); shutdownTimestamp = System.currentTimeMillis(); - if (shutdownDone.isNotInInitialState()) { - try { - shutdownDone.checkIfSuccessOrWait(); - } catch (NoResponseException | InterruptedException e) { - LOGGER.log(Level.WARNING, "shutdownDone was not marked as successful by the writer thread", e); - } - } } /** @@ -1370,7 +1342,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } writer.write(packet.toXML().toString()); } - writer.flush(); } catch (Exception e) { LOGGER.log(Level.WARNING, @@ -1407,9 +1378,6 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { } else { 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. if (writerException != null) { @@ -1721,7 +1689,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { * @return true if Stream Management was negotiated. */ public boolean isSmEnabled() { - return smEnabledSyncPoint.wasSuccessful(); + return smEnabledSyncPoint; } /** @@ -1730,7 +1698,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { * @return true if the stream was resumed. */ public boolean streamWasResumed() { - return smResumedSyncPoint.wasSuccessful(); + return smResumedSyncPoint == SyncPointState.successful; } /** diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnectionConfiguration.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnectionConfiguration.java index aaccfacb6..e4bb64217 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnectionConfiguration.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnectionConfiguration.java @@ -26,7 +26,7 @@ import org.jivesoftware.smack.ConnectionConfiguration; *

*
  * {@code
- * XMPPTCPConnectionConfiguration conf = XMPPConnectionConfiguration.builder()
+ * XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder()
  *     .setXmppDomain("example.org").setUsernameAndPassword("user", "password")
  *     .setCompressionEnabled(false).build();
  * XMPPTCPConnection connection = new XMPPTCPConnection(conf);
diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java
index 74286fdd9..e48c35b25 100644
--- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java
+++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XmppTcpTransportModule.java
@@ -47,17 +47,13 @@ import javax.net.ssl.SSLSession;
 
 import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
 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.SecurityRequiredByServerException;
-import org.jivesoftware.smack.SmackException.SmackWrappedException;
+import org.jivesoftware.smack.SmackException.SmackCertificateException;
 import org.jivesoftware.smack.SmackFuture;
 import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
 import org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment;
-import org.jivesoftware.smack.XMPPException.FailedNonzaException;
+import org.jivesoftware.smack.XMPPException;
 import org.jivesoftware.smack.XmppInputOutputFilter;
 import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
 import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.LookupRemoteConnectionEndpointsStateDescriptor;
@@ -591,7 +587,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
 
         @Override
         protected List> lookupConnectionEndpoints() {
-            // Assert that there are no stale discovred endpoints prior performing the lookup.
+            // Assert that there are no stale discovered endpoints prior performing the lookup.
             assert discoveredTcpEndpoints == null;
 
             List> futures = new ArrayList<>(2);
@@ -744,23 +740,14 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
 
         @Override
         public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
-                        throws InterruptedException, ConnectionUnexpectedTerminatedException, NotConnectedException,
-                        NoResponseException, IOException {
+                        throws InterruptedException, IOException, SmackException, XMPPException {
             // The fields inetSocketAddress and failedAddresses are handed over from LookupHostAddresses to
             // ConnectingToHost.
             ConnectionAttemptState connectionAttemptState = new ConnectionAttemptState(connectionInternal, discoveredTcpEndpoints,
                     this);
-            connectionAttemptState.establishTcpConnection();
-
-            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);
+            StateTransitionResult.Failure failure = connectionAttemptState.establishTcpConnection();
+            if (failure != null) {
+                return failure;
             }
 
             socketChannel = connectionAttemptState.socketChannel;
@@ -858,8 +845,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
 
         @Override
         public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
-                        throws SmackWrappedException, FailedNonzaException, IOException, InterruptedException,
-                        ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
+                        throws IOException, InterruptedException, SmackException, XMPPException {
             connectionInternal.sendAndWaitForResponse(StartTls.INSTANCE, TlsProceed.class, TlsFailure.class);
 
             SmackTlsContext smackTlsContext = connectionInternal.getSmackTlsContext();
@@ -881,7 +867,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
             try {
                 tlsState.waitForHandshakeFinished();
             } catch (CertificateException e) {
-                throw new SmackWrappedException(e);
+                throw new SmackCertificateException(e);
             }
 
             connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after TLS established");
@@ -1166,43 +1152,20 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
         private void handleSslException(SSLException e) {
             handshakeException = e;
             handshakeStatus = TlsHandshakeStatus.failed;
-            synchronized (this) {
-                notifyAll();
-            }
+            connectionInternal.notifyWaitingThreads();
         }
 
         private void onHandshakeFinished() {
             handshakeStatus = TlsHandshakeStatus.successful;
-            synchronized (this) {
-                notifyAll();
-            }
+            connectionInternal.notifyWaitingThreads();
         }
 
         private boolean isHandshakeFinished() {
             return handshakeStatus == TlsHandshakeStatus.successful || handshakeStatus == TlsHandshakeStatus.failed;
         }
 
-        private void waitForHandshakeFinished() throws InterruptedException, CertificateException, SSLException, ConnectionUnexpectedTerminatedException, NoResponseException {
-            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");
-            }
+        private void waitForHandshakeFinished() throws InterruptedException, CertificateException, SSLException, SmackException, XMPPException {
+            connectionInternal.waitForCondition(() -> isHandshakeFinished(), "TLS handshake to finish");
 
             if (handshakeStatus == TlsHandshakeStatus.failed) {
                 throw handshakeException;
@@ -1235,7 +1198,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
 
         @Override
         public void waitUntilInputOutputClosed() throws IOException, CertificateException, InterruptedException,
-                ConnectionUnexpectedTerminatedException, NoResponseException {
+                SmackException, XMPPException {
             waitForHandshakeFinished();
         }
 
diff --git a/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java b/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java
index 95f74bc60..b6a2ab6a9 100644
--- a/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java
+++ b/smack-tcp/src/test/java/org/jivesoftware/smack/tcp/PacketWriterTest.java
@@ -1,6 +1,6 @@
 /**
  *
- * Copyright 2014-2019 Florian Schmaus
+ * Copyright 2014-2020 Florian Schmaus
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -124,8 +124,6 @@ public class PacketWriterTest {
         // Not really cool, but may increases the chances for 't' to block in sendStanza.
         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
         // thread created above.
         pw.shutdown(false);
diff --git a/version b/version
index 92560b19f..c4b97782d 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-4.4.0-alpha4-SNAPSHOT
+4.4.0-alpha5-SNAPSHOT