mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2025-09-10 18:59:41 +02:00
Remove SynchronizationPoint
This continues the design started with e98d42790
("SmackReactor/NIO,
Java8/Android19, Pretty print XML, FSM connections"), where the
exceptions that caused an operation to fail, are not recorded within
SynchronizationPoint but within the connection instance itself.
This commit is contained in:
parent
b1a4ccfae8
commit
57961a8cc1
15 changed files with 352 additions and 684 deletions
|
@ -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<SmackException> tlsHandled = new SynchronizationPoint<>(this, "establishing TLS");
|
||||
protected SmackException currentSmackException;
|
||||
protected XMPPException currentXmppException;
|
||||
|
||||
protected boolean tlsHandled;
|
||||
|
||||
/**
|
||||
* Set to success if the last features stanza from the server has been parsed. A XMPP connection
|
||||
* Set to <code>true</code> if the last features stanza from the server has been parsed. A XMPP connection
|
||||
* handshake can invoke multiple features stanzas, e.g. when TLS is activated a second feature
|
||||
* stanza is send by the server. This is set to true once the last feature stanza has been
|
||||
* parsed.
|
||||
*/
|
||||
protected final SynchronizationPoint<SmackException> 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 <code>true</code> if the SASL feature has been received.
|
||||
*/
|
||||
protected final SynchronizationPoint<XMPPException> 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<Exception> 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<QName, IQRequestHandler> setIqRequestHandler = new HashMap<>();
|
||||
private final Map<QName, IQRequestHandler> 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<Boolean> condition) throws InterruptedException {
|
||||
final long deadline = System.currentTimeMillis() + getReplyTimeout();
|
||||
synchronized (internalMonitor) {
|
||||
while (!condition.get().booleanValue() && !hasCurrentConnectionException()) {
|
||||
final long now = System.currentTimeMillis();
|
||||
if (now >= deadline) {
|
||||
return false;
|
||||
}
|
||||
internalMonitor.wait(deadline - now);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected final void waitForCondition(Supplier<Boolean> condition, String waitFor) throws InterruptedException, NoResponseException {
|
||||
boolean success = waitForCondition(condition);
|
||||
if (!success) {
|
||||
throw NoResponseException.newWith(this, waitFor);
|
||||
}
|
||||
}
|
||||
|
||||
protected final void waitForConditionOrThrowConnectionException(Supplier<Boolean> condition, String waitFor) throws InterruptedException, SmackException, XMPPException {
|
||||
waitForCondition(condition, waitFor);
|
||||
if (hasCurrentConnectionException()) {
|
||||
throwCurrentConnectionException();
|
||||
}
|
||||
}
|
||||
|
||||
protected Resourcepart bindResourceAndEstablishSession(Resourcepart resource)
|
||||
throws SmackException, InterruptedException, XMPPException {
|
||||
// Wait until either:
|
||||
// - 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
|
||||
|
@ -905,29 +976,20 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove this async but ordered?
|
||||
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.
|
||||
|
@ -947,19 +1009,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 +1873,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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1829,7 +1885,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
|||
|| !config.isCompressionEnabled()) {
|
||||
// This was was last features from the server is either it did not contain
|
||||
// compression or if we disabled it
|
||||
lastFeaturesReceived.reportSuccess();
|
||||
lastFeaturesReceived = true;
|
||||
notifyWaitingThreads();
|
||||
}
|
||||
}
|
||||
afterFeaturesReceived();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,352 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* Copyright © 2014-2019 Florian Schmaus
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jivesoftware.smack;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||
import org.jivesoftware.smack.SmackException.SmackWrappedException;
|
||||
import org.jivesoftware.smack.packet.Nonza;
|
||||
import org.jivesoftware.smack.packet.Stanza;
|
||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||
|
||||
public class SynchronizationPoint<E extends Exception> {
|
||||
|
||||
private final AbstractXMPPConnection connection;
|
||||
private final Lock connectionLock;
|
||||
private final Condition condition;
|
||||
private final String waitFor;
|
||||
|
||||
// Note that there is no need to make 'state' and 'failureException' volatile. Since 'lock' and 'unlock' have the
|
||||
// same memory synchronization effects as synchronization block enter and leave.
|
||||
private State state;
|
||||
private E failureException;
|
||||
private SmackWrappedException smackWrappedExcpetion;
|
||||
|
||||
private volatile long waitStart;
|
||||
|
||||
/**
|
||||
* Construct a new synchronization point for the given connection.
|
||||
*
|
||||
* @param connection the connection of this synchronization point.
|
||||
* @param waitFor a description of the event this synchronization point handles.
|
||||
*/
|
||||
public SynchronizationPoint(AbstractXMPPConnection connection, String waitFor) {
|
||||
this.connection = connection;
|
||||
this.connectionLock = connection.getConnectionLock();
|
||||
this.condition = connection.getConnectionLock().newCondition();
|
||||
this.waitFor = waitFor;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize (or reset) this synchronization point.
|
||||
*/
|
||||
@SuppressWarnings("LockNotBeforeTry")
|
||||
public void init() {
|
||||
connectionLock.lock();
|
||||
state = State.Initial;
|
||||
failureException = null;
|
||||
smackWrappedExcpetion = null;
|
||||
connectionLock.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the given top level stream element and wait for a response.
|
||||
*
|
||||
* @param request the plain stream element to send.
|
||||
* @throws NoResponseException if no response was received.
|
||||
* @throws NotConnectedException if the connection is not connected.
|
||||
* @throws InterruptedException if the connection is interrupted.
|
||||
* @return <code>null</code> if synchronization point was successful, or the failure Exception.
|
||||
*/
|
||||
public Exception sendAndWaitForResponse(TopLevelStreamElement request) throws NoResponseException,
|
||||
NotConnectedException, InterruptedException {
|
||||
assert state == State.Initial;
|
||||
connectionLock.lock();
|
||||
try {
|
||||
if (request != null) {
|
||||
if (request instanceof Stanza) {
|
||||
connection.sendStanza((Stanza) request);
|
||||
}
|
||||
else if (request instanceof Nonza) {
|
||||
connection.sendNonza((Nonza) request);
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported element type");
|
||||
}
|
||||
state = State.RequestSent;
|
||||
}
|
||||
waitForConditionOrTimeout();
|
||||
}
|
||||
finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
return checkForResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the given plain stream element and wait for a response.
|
||||
*
|
||||
* @param request the plain stream element to send.
|
||||
* @throws E if an failure was reported.
|
||||
* @throws NoResponseException if no response was received.
|
||||
* @throws NotConnectedException if the connection is not connected.
|
||||
* @throws InterruptedException if the connection is interrupted.
|
||||
* @throws SmackWrappedException in case of a wrapped exception;
|
||||
*/
|
||||
public void sendAndWaitForResponseOrThrow(Nonza request) throws E, NoResponseException,
|
||||
NotConnectedException, InterruptedException, SmackWrappedException {
|
||||
sendAndWaitForResponse(request);
|
||||
switch (state) {
|
||||
case Failure:
|
||||
throwException();
|
||||
break;
|
||||
default:
|
||||
// Success, do nothing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this synchronization point is successful or wait the connections reply timeout.
|
||||
* @throws NoResponseException if there was no response marking the synchronization point as success or failed.
|
||||
* @throws E if there was a failure
|
||||
* @throws InterruptedException if the connection is interrupted.
|
||||
* @throws SmackWrappedException in case of a wrapped exception;
|
||||
*/
|
||||
public void checkIfSuccessOrWaitOrThrow() throws NoResponseException, E, InterruptedException, SmackWrappedException {
|
||||
checkIfSuccessOrWait();
|
||||
if (state == State.Failure) {
|
||||
throwException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this synchronization point is successful or wait the connections reply timeout.
|
||||
* @throws NoResponseException if there was no response marking the synchronization point as success or failed.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
* @return <code>null</code> if synchronization point was successful, or the failure Exception.
|
||||
*/
|
||||
public Exception checkIfSuccessOrWait() throws NoResponseException, InterruptedException {
|
||||
connectionLock.lock();
|
||||
try {
|
||||
switch (state) {
|
||||
// Return immediately on success or failure
|
||||
case Success:
|
||||
return null;
|
||||
case Failure:
|
||||
return getException();
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
waitForConditionOrTimeout();
|
||||
} finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
return checkForResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Report this synchronization point as successful.
|
||||
*/
|
||||
public void reportSuccess() {
|
||||
connectionLock.lock();
|
||||
try {
|
||||
state = State.Success;
|
||||
condition.signalAll();
|
||||
}
|
||||
finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated.
|
||||
* @deprecated use {@link #reportFailure(Exception)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void reportFailure() {
|
||||
reportFailure(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report this synchronization point as failed because of the given exception. The {@code failureException} must be set.
|
||||
*
|
||||
* @param failureException the exception causing this synchronization point to fail.
|
||||
*/
|
||||
public void reportFailure(E failureException) {
|
||||
assert failureException != null;
|
||||
connectionLock.lock();
|
||||
try {
|
||||
state = State.Failure;
|
||||
this.failureException = failureException;
|
||||
condition.signalAll();
|
||||
}
|
||||
finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report this synchronization point as failed because of the given exception. The {@code failureException} must be set.
|
||||
*
|
||||
* @param exception the exception causing this synchronization point to fail.
|
||||
*/
|
||||
public void reportGenericFailure(SmackWrappedException exception) {
|
||||
assert exception != null;
|
||||
connectionLock.lock();
|
||||
try {
|
||||
state = State.Failure;
|
||||
this.smackWrappedExcpetion = exception;
|
||||
condition.signalAll();
|
||||
}
|
||||
finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this synchronization point was successful.
|
||||
*
|
||||
* @return true if the synchronization point was successful, false otherwise.
|
||||
*/
|
||||
public boolean wasSuccessful() {
|
||||
connectionLock.lock();
|
||||
try {
|
||||
return state == State.Success;
|
||||
}
|
||||
finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNotInInitialState() {
|
||||
connectionLock.lock();
|
||||
try {
|
||||
return state != State.Initial;
|
||||
}
|
||||
finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this synchronization point has its request already sent.
|
||||
*
|
||||
* @return true if the request was already sent, false otherwise.
|
||||
*/
|
||||
public boolean requestSent() {
|
||||
connectionLock.lock();
|
||||
try {
|
||||
return state == State.RequestSent;
|
||||
}
|
||||
finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public E getFailureException() {
|
||||
connectionLock.lock();
|
||||
try {
|
||||
return failureException;
|
||||
}
|
||||
finally {
|
||||
connectionLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTimeout() {
|
||||
waitStart = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the condition to become something else as {@link State#RequestSent} or {@link State#Initial}.
|
||||
* {@link #reportSuccess()}, {@link #reportFailure()} and {@link #reportFailure(Exception)} will either set this
|
||||
* synchronization point to {@link State#Success} or {@link State#Failure}. If none of them is set after the
|
||||
* connections reply timeout, this method will set the state of {@link State#NoResponse}.
|
||||
* @throws InterruptedException if the calling thread was interrupted.
|
||||
*/
|
||||
private void waitForConditionOrTimeout() throws InterruptedException {
|
||||
waitStart = System.currentTimeMillis();
|
||||
while (state == State.RequestSent || state == State.Initial) {
|
||||
long timeout = connection.getReplyTimeout();
|
||||
long remainingWaitMillis = timeout - (System.currentTimeMillis() - waitStart);
|
||||
long remainingWait = TimeUnit.MILLISECONDS.toNanos(remainingWaitMillis);
|
||||
|
||||
if (remainingWait <= 0) {
|
||||
state = State.NoResponse;
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
condition.awaitNanos(remainingWait);
|
||||
} catch (InterruptedException e) {
|
||||
state = State.Interrupted;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Exception getException() {
|
||||
if (failureException != null) {
|
||||
return failureException;
|
||||
}
|
||||
return smackWrappedExcpetion;
|
||||
}
|
||||
|
||||
private void throwException() throws E, SmackWrappedException {
|
||||
if (failureException != null) {
|
||||
throw failureException;
|
||||
}
|
||||
throw smackWrappedExcpetion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a response and throw a {@link NoResponseException} if there was none.
|
||||
* <p>
|
||||
* The exception is thrown, if state is one of 'Initial', 'NoResponse' or 'RequestSent'
|
||||
* </p>
|
||||
* @return <code>true</code> if synchronization point was successful, <code>false</code> on failure.
|
||||
* @throws NoResponseException if there was no response from the remote entity.
|
||||
*/
|
||||
private Exception checkForResponse() throws NoResponseException {
|
||||
switch (state) {
|
||||
case Initial:
|
||||
case NoResponse:
|
||||
case RequestSent:
|
||||
throw NoResponseException.newWith(connection, waitFor);
|
||||
case Success:
|
||||
return null;
|
||||
case Failure:
|
||||
return getException();
|
||||
default:
|
||||
throw new AssertionError("Unknown state " + state);
|
||||
}
|
||||
}
|
||||
|
||||
private enum State {
|
||||
Initial,
|
||||
RequestSent,
|
||||
NoResponse,
|
||||
Success,
|
||||
Failure,
|
||||
Interrupted,
|
||||
}
|
||||
}
|
|
@ -67,7 +67,8 @@ public interface XmppInputOutputFilter {
|
|||
default void closeInputOutput() {
|
||||
}
|
||||
|
||||
default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException, InterruptedException, SmackException {
|
||||
default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException,
|
||||
InterruptedException, SmackException, XMPPException {
|
||||
}
|
||||
|
||||
Object getStats();
|
||||
|
|
|
@ -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<Boolean> 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<State> 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<State> initialStateVertex = currentStateVertex;
|
||||
final State initialState = initialStateVertex.getElement();
|
||||
|
@ -359,15 +365,13 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
|||
* @param walkStateGraphContext the "walk state graph" context.
|
||||
* @return A state transition result or <code>null</code> 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<State> successorStateVertex,
|
||||
WalkStateGraphContext walkStateGraphContext) throws SmackException, XMPPErrorException,
|
||||
SASLErrorException, IOException, InterruptedException, FailedNonzaException {
|
||||
WalkStateGraphContext walkStateGraphContext) throws SmackException, XMPPException,
|
||||
IOException, InterruptedException {
|
||||
final GraphVertex<State> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<XmppInputOutputFilter> 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<Boolean> condition, String waitFor) throws InterruptedException, SmackException, XMPPException;
|
||||
|
||||
public abstract void notifyWaitingThreads();
|
||||
|
||||
public abstract void setCompressionEnabled(boolean compressionEnabled);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
*
|
||||
* 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.
|
||||
* 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.util;
|
||||
|
||||
// TODO: Replace with java.util.function.Supplier once Smack's minimum Android SDK level is 24 or higher.
|
||||
public interface Supplier<T> {
|
||||
|
||||
T get();
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue