mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-12-25 12:08:00 +01:00
Add non-blocking send
This commit is contained in:
parent
711d7d92bd
commit
c77948bb91
9 changed files with 237 additions and 101 deletions
|
@ -29,17 +29,19 @@ import org.jivesoftware.smack.AbstractXMPPConnection;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.GenericConnectionException;
|
import org.jivesoftware.smack.SmackException.GenericConnectionException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
|
||||||
import org.jivesoftware.smack.SmackException.SmackWrappedException;
|
import org.jivesoftware.smack.SmackException.SmackWrappedException;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
import org.jivesoftware.smack.XMPPException.StreamErrorException;
|
||||||
import org.jivesoftware.smack.packet.Element;
|
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Nonza;
|
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.packet.StanzaError;
|
import org.jivesoftware.smack.packet.StanzaError;
|
||||||
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
|
||||||
|
import org.jivesoftware.smack.util.Async;
|
||||||
import org.jivesoftware.smack.util.CloseableUtil;
|
import org.jivesoftware.smack.util.CloseableUtil;
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParser;
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
|
@ -90,6 +92,10 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
@SuppressWarnings("HidingField")
|
@SuppressWarnings("HidingField")
|
||||||
private final BOSHConfiguration config;
|
private final BOSHConfiguration config;
|
||||||
|
|
||||||
|
private final ArrayBlockingQueueWithShutdown<TopLevelStreamElement> outgoingQueue = new ArrayBlockingQueueWithShutdown<>(100, true);
|
||||||
|
|
||||||
|
private Thread writerThread;
|
||||||
|
|
||||||
// Some flags which provides some info about the current state.
|
// Some flags which provides some info about the current state.
|
||||||
private boolean isFirstInitialization = true;
|
private boolean isFirstInitialization = true;
|
||||||
private boolean done = false;
|
private boolean done = false;
|
||||||
|
@ -194,11 +200,16 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert writerThread == null || !writerThread.isAlive();
|
||||||
|
outgoingQueue.start();
|
||||||
|
writerThread = Async.go(this::writeElements, this + " Writer");
|
||||||
|
|
||||||
// If there is no feedback, throw an remote server timeout error
|
// If there is no feedback, throw an remote server timeout error
|
||||||
if (!connected && !done) {
|
if (!connected && !done) {
|
||||||
done = true;
|
done = true;
|
||||||
String errorMessage = "Timeout reached for the connection to "
|
String errorMessage = "Timeout reached for the connection to "
|
||||||
+ getHost() + ":" + getPort() + ".";
|
+ getHost() + ":" + getPort() + ".";
|
||||||
|
instantShutdown();
|
||||||
throw new SmackException.SmackMessageException(errorMessage);
|
throw new SmackException.SmackMessageException(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,6 +218,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'/>");
|
"<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'/>");
|
||||||
onStreamOpen(parser);
|
onStreamOpen(parser);
|
||||||
} catch (XmlPullParserException | IOException e) {
|
} catch (XmlPullParserException | IOException e) {
|
||||||
|
instantShutdown();
|
||||||
throw new AssertionError("Failed to setup stream environment", e);
|
throw new AssertionError("Failed to setup stream environment", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,40 +246,92 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
afterSuccessfulLogin(false);
|
afterSuccessfulLogin(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private volatile boolean writerThreadRunning;
|
||||||
public void sendNonza(Nonza element) throws NotConnectedException {
|
|
||||||
if (done) {
|
|
||||||
throw new NotConnectedException();
|
|
||||||
}
|
|
||||||
sendElement(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private void writeElements() {
|
||||||
protected void sendStanzaInternal(Stanza packet) throws NotConnectedException {
|
writerThreadRunning = true;
|
||||||
sendElement(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendElement(Element element) {
|
|
||||||
try {
|
try {
|
||||||
send(ComposableBody.builder().setPayloadXML(element.toXML(BOSH_URI).toString()).build());
|
while (true) {
|
||||||
if (element instanceof Stanza) {
|
TopLevelStreamElement element;
|
||||||
firePacketSendingListeners((Stanza) element);
|
try {
|
||||||
|
element = outgoingQueue.take();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOGGER.log(Level.FINE,
|
||||||
|
"Writer thread exiting: Outgoing queue was shutdown as signalled by interrupted exception",
|
||||||
|
e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String xmlPayload = element.toXML(BOSH_URI).toString();
|
||||||
|
ComposableBody.Builder composableBodyBuilder = ComposableBody.builder().setPayloadXML(xmlPayload);
|
||||||
|
if (sessionID != null) {
|
||||||
|
BodyQName qName = BodyQName.create(BOSH_URI, "sid");
|
||||||
|
composableBodyBuilder.setAttribute(qName, sessionID);
|
||||||
|
}
|
||||||
|
|
||||||
|
ComposableBody composableBody = composableBodyBuilder.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
client.send(composableBody);
|
||||||
|
} catch (BOSHException e) {
|
||||||
|
LOGGER.log(Level.WARNING, this + " received BOSHException in writer thread, connection broke!", e);
|
||||||
|
// TODO: Signal the user that there was an unexpected exception.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element instanceof Stanza) {
|
||||||
|
Stanza stanza = (Stanza) element;
|
||||||
|
firePacketSendingListeners(stanza);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch (Exception exception) {
|
||||||
catch (BOSHException e) {
|
LOGGER.log(Level.WARNING, "BOSH writer thread threw", exception);
|
||||||
LOGGER.log(Level.SEVERE, "BOSHException in sendStanzaInternal", e);
|
} finally {
|
||||||
|
writerThreadRunning = false;
|
||||||
|
notifyWaitingThreads();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sendInternal(TopLevelStreamElement element) throws NotConnectedException, InterruptedException {
|
||||||
|
throwNotConnectedExceptionIfAppropriate();
|
||||||
|
try {
|
||||||
|
outgoingQueue.put(element);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throwNotConnectedExceptionIfAppropriate();
|
||||||
|
// If the method above did not throw, then the sending thread was interrupted
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sendNonBlockingInternal(TopLevelStreamElement element)
|
||||||
|
throws NotConnectedException, OutgoingQueueFullException {
|
||||||
|
throwNotConnectedExceptionIfAppropriate();
|
||||||
|
boolean enqueued = outgoingQueue.offer(element);
|
||||||
|
if (!enqueued) {
|
||||||
|
throwNotConnectedExceptionIfAppropriate();
|
||||||
|
throw new OutgoingQueueFullException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the connection by setting presence to unavailable and closing the
|
|
||||||
* HTTP client. The shutdown logic will be used during a planned disconnection or when
|
|
||||||
* dealing with an unexpected disconnection. Unlike {@link #disconnect()} the connection's
|
|
||||||
* BOSH stanza reader will not be removed; thus connection's state is kept.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected void shutdown() {
|
protected void shutdown() {
|
||||||
|
instantShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void instantShutdown() {
|
||||||
|
outgoingQueue.shutdown();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean writerThreadTerminated = waitFor(() -> !writerThreadRunning);
|
||||||
|
if (!writerThreadTerminated) {
|
||||||
|
LOGGER.severe("Writer thread of " + this + " did not terminate timely");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOGGER.log(Level.FINE, "Interrupted while waiting for writer thread to terminate", e);
|
||||||
|
}
|
||||||
|
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
try {
|
try {
|
||||||
|
@ -275,20 +339,15 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOGGER.log(Level.WARNING, "shutdown", e);
|
LOGGER.log(Level.WARNING, "shutdown", e);
|
||||||
}
|
}
|
||||||
client = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instantShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void instantShutdown() {
|
|
||||||
setWasAuthenticated();
|
setWasAuthenticated();
|
||||||
sessionID = null;
|
sessionID = null;
|
||||||
done = true;
|
done = true;
|
||||||
authenticated = false;
|
authenticated = false;
|
||||||
connected = false;
|
connected = false;
|
||||||
isFirstInitialization = false;
|
isFirstInitialization = false;
|
||||||
|
client = null;
|
||||||
|
|
||||||
// Close down the readers and writers.
|
// Close down the readers and writers.
|
||||||
CloseableUtil.maybeClose(readerPipe, LOGGER);
|
CloseableUtil.maybeClose(readerPipe, LOGGER);
|
||||||
|
@ -410,14 +469,15 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
|
||||||
// XMPP over BOSH is unusual when it comes to SASL authentication: Instead of sending a new stream open, it
|
// XMPP over BOSH is unusual when it comes to SASL authentication: Instead of sending a new stream open, it
|
||||||
// requires a special XML element ot be send after successful SASL authentication.
|
// requires a special XML element ot be send after successful SASL authentication.
|
||||||
// See XEP-0206 § 5., especially the following is example 5 of XEP-0206.
|
// See XEP-0206 § 5., especially the following is example 5 of XEP-0206.
|
||||||
ComposableBody composeableBody = ComposableBody.builder().setNamespaceDefinition("xmpp",
|
ComposableBody composeableBody = ComposableBody.builder()
|
||||||
XMPPBOSHConnection.XMPP_BOSH_NS).setAttribute(
|
.setNamespaceDefinition("xmpp", XMPPBOSHConnection.XMPP_BOSH_NS)
|
||||||
BodyQName.createWithPrefix(XMPPBOSHConnection.XMPP_BOSH_NS, "restart",
|
.setAttribute(BodyQName.createWithPrefix(XMPPBOSHConnection.XMPP_BOSH_NS, "restart", "xmpp"), "true")
|
||||||
"xmpp"), "true").setAttribute(
|
.setAttribute(BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"), getXMPPServiceDomain().toString())
|
||||||
BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"), getXMPPServiceDomain().toString()).build();
|
.setAttribute(BodyQName.create(BOSH_URI, "sid"), sessionID)
|
||||||
|
.build();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
send(composeableBody);
|
client.send(composeableBody);
|
||||||
} catch (BOSHException e) {
|
} catch (BOSHException e) {
|
||||||
// jbosh's exception API does not really match the one of Smack.
|
// jbosh's exception API does not really match the one of Smack.
|
||||||
throw new SmackException.SmackWrappedException(e);
|
throw new SmackException.SmackWrappedException(e);
|
||||||
|
|
|
@ -51,6 +51,7 @@ import org.jivesoftware.smack.SmackException.AlreadyLoggedInException;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||||
|
import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
|
||||||
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
|
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
|
||||||
import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
|
import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
|
||||||
import org.jivesoftware.smack.SmackException.SecurityRequiredException;
|
import org.jivesoftware.smack.SmackException.SecurityRequiredException;
|
||||||
|
@ -460,8 +461,17 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
@Override
|
@Override
|
||||||
public abstract boolean isSecureConnection();
|
public abstract boolean isSecureConnection();
|
||||||
|
|
||||||
protected abstract void sendStanzaInternal(Stanza packet) throws NotConnectedException, InterruptedException;
|
// Usually batching is a good idea. So the two
|
||||||
|
// send(Internal|NonBlockingInternal) methods below could be using
|
||||||
|
// Collection<? extends TopLevelStreamElement> as parameter type instead.
|
||||||
|
// TODO: Add "batched send" support. Note that for the non-blocking variant, this probably requires a change in
|
||||||
|
// return type, so that it is possible to signal which messages could be "send" and which not.
|
||||||
|
|
||||||
|
protected abstract void sendInternal(TopLevelStreamElement element) throws NotConnectedException, InterruptedException;
|
||||||
|
|
||||||
|
protected abstract void sendNonBlockingInternal(TopLevelStreamElement element) throws NotConnectedException, OutgoingQueueFullException;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public boolean trySendStanza(Stanza stanza) throws NotConnectedException {
|
public boolean trySendStanza(Stanza stanza) throws NotConnectedException {
|
||||||
// Default implementation which falls back to sendStanza() as mentioned in the methods javadoc. May be
|
// Default implementation which falls back to sendStanza() as mentioned in the methods javadoc. May be
|
||||||
|
@ -476,6 +486,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public boolean trySendStanza(Stanza stanza, long timeout, TimeUnit unit)
|
public boolean trySendStanza(Stanza stanza, long timeout, TimeUnit unit)
|
||||||
throws NotConnectedException, InterruptedException {
|
throws NotConnectedException, InterruptedException {
|
||||||
|
@ -486,7 +497,14 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract void sendNonza(Nonza element) throws NotConnectedException, InterruptedException;
|
public final void sendNonza(Nonza nonza) throws NotConnectedException, InterruptedException {
|
||||||
|
sendInternal(nonza);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void sendNonzaNonBlocking(Nonza nonza) throws NotConnectedException, OutgoingQueueFullException {
|
||||||
|
sendNonBlockingInternal(nonza);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract boolean isUsingCompression();
|
public abstract boolean isUsingCompression();
|
||||||
|
@ -853,8 +871,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
return stanzaFactory;
|
return stanzaFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private Stanza preSendStanza(Stanza stanza) throws NotConnectedException {
|
||||||
public final void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
|
|
||||||
Objects.requireNonNull(stanza, "Stanza must not be null");
|
Objects.requireNonNull(stanza, "Stanza must not be null");
|
||||||
assert stanza instanceof Message || stanza instanceof Presence || stanza instanceof IQ;
|
assert stanza instanceof Message || stanza instanceof Presence || stanza instanceof IQ;
|
||||||
|
|
||||||
|
@ -873,7 +890,19 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
// Invoke interceptors for the new stanza that is about to be sent. Interceptors may modify
|
// Invoke interceptors for the new stanza that is about to be sent. Interceptors may modify
|
||||||
// the content of the stanza.
|
// the content of the stanza.
|
||||||
Stanza stanzaAfterInterceptors = firePacketInterceptors(stanza);
|
Stanza stanzaAfterInterceptors = firePacketInterceptors(stanza);
|
||||||
sendStanzaInternal(stanzaAfterInterceptors);
|
return stanzaAfterInterceptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException {
|
||||||
|
stanza = preSendStanza(stanza);
|
||||||
|
sendInternal(stanza);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void sendStanzaNonBlocking(Stanza stanza) throws NotConnectedException, OutgoingQueueFullException {
|
||||||
|
stanza = preSendStanza(stanza);
|
||||||
|
sendNonBlockingInternal(stanza);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2006,18 +2035,11 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|
||||||
}, timeout, TimeUnit.MILLISECONDS);
|
}, timeout, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
addAsyncStanzaListener(stanzaListener, replyFilter);
|
addAsyncStanzaListener(stanzaListener, replyFilter);
|
||||||
Runnable sendOperation = () -> {
|
try {
|
||||||
try {
|
sendStanzaNonBlocking(stanza);
|
||||||
sendStanza(stanza);
|
}
|
||||||
}
|
catch (NotConnectedException | OutgoingQueueFullException exception) {
|
||||||
catch (NotConnectedException | InterruptedException exception) {
|
future.setException(exception);
|
||||||
future.setException(exception);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (SmackConfiguration.TRUELY_ASYNC_SENDS) {
|
|
||||||
Async.go(sendOperation);
|
|
||||||
} else {
|
|
||||||
sendOperation.run();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
|
|
|
@ -387,13 +387,4 @@ public final class SmackConfiguration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If enabled, causes {@link AbstractXMPPConnection} to create a thread for every asynchronous send operation. This
|
|
||||||
* is meant to work-around a shortcoming of Smack 4.4, where certain send operations are not asynchronous even if
|
|
||||||
* they should be. This is an expert setting, do not toggle if you do not understand the consequences or have been
|
|
||||||
* told to do so. Note that it is expected that this will not be needed in future Smack versions.
|
|
||||||
*
|
|
||||||
* @since 4.4.6
|
|
||||||
*/
|
|
||||||
public static boolean TRUELY_ASYNC_SENDS = false;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,6 +206,12 @@ public abstract class SmackException extends Exception {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class OutgoingQueueFullException extends SmackException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static class IllegalStateChangeException extends SmackException {
|
public static class IllegalStateChangeException extends SmackException {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,6 +22,7 @@ import javax.xml.namespace.QName;
|
||||||
|
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
|
||||||
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
|
||||||
import org.jivesoftware.smack.filter.IQReplyFilter;
|
import org.jivesoftware.smack.filter.IQReplyFilter;
|
||||||
import org.jivesoftware.smack.filter.StanzaFilter;
|
import org.jivesoftware.smack.filter.StanzaFilter;
|
||||||
|
@ -199,6 +200,8 @@ public interface XMPPConnection {
|
||||||
* */
|
* */
|
||||||
void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException;
|
void sendStanza(Stanza stanza) throws NotConnectedException, InterruptedException;
|
||||||
|
|
||||||
|
void sendStanzaNonBlocking(Stanza stanza) throws NotConnectedException, OutgoingQueueFullException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to send the given stanza. Returns {@code true} if the stanza was successfully put into the outgoing stanza
|
* Try to send the given stanza. Returns {@code true} if the stanza was successfully put into the outgoing stanza
|
||||||
* queue, otherwise, if {@code false} is returned, the stanza could not be scheduled for sending (for example
|
* queue, otherwise, if {@code false} is returned, the stanza could not be scheduled for sending (for example
|
||||||
|
@ -213,7 +216,10 @@ public interface XMPPConnection {
|
||||||
* @return {@code true} if the stanza was successfully scheduled to be send, {@code false} otherwise.
|
* @return {@code true} if the stanza was successfully scheduled to be send, {@code false} otherwise.
|
||||||
* @throws NotConnectedException if the connection is not connected.
|
* @throws NotConnectedException if the connection is not connected.
|
||||||
* @since 4.4.0
|
* @since 4.4.0
|
||||||
|
* @deprecated use {@link #sendStanzaNonBlocking(Stanza)} instead.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Remove in Smack 4.7.
|
||||||
|
@Deprecated
|
||||||
boolean trySendStanza(Stanza stanza) throws NotConnectedException;
|
boolean trySendStanza(Stanza stanza) throws NotConnectedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -234,7 +240,10 @@ public interface XMPPConnection {
|
||||||
* @throws NotConnectedException if the connection is not connected.
|
* @throws NotConnectedException if the connection is not connected.
|
||||||
* @throws InterruptedException if the calling thread was interrupted.
|
* @throws InterruptedException if the calling thread was interrupted.
|
||||||
* @since 4.4.0
|
* @since 4.4.0
|
||||||
|
* @deprecated use {@link #sendStanzaNonBlocking(Stanza)} instead.
|
||||||
*/
|
*/
|
||||||
|
// TODO: Remove in Smack 4.7.
|
||||||
|
@Deprecated
|
||||||
boolean trySendStanza(Stanza stanza, long timeout, TimeUnit unit) throws NotConnectedException, InterruptedException;
|
boolean trySendStanza(Stanza stanza, long timeout, TimeUnit unit) throws NotConnectedException, InterruptedException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -251,6 +260,8 @@ public interface XMPPConnection {
|
||||||
*/
|
*/
|
||||||
void sendNonza(Nonza nonza) throws NotConnectedException, InterruptedException;
|
void sendNonza(Nonza nonza) throws NotConnectedException, InterruptedException;
|
||||||
|
|
||||||
|
void sendNonzaNonBlocking(Nonza stanza) throws NotConnectedException, OutgoingQueueFullException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a connection listener to this connection that will be notified when
|
* Adds a connection listener to this connection that will be notified when
|
||||||
* the connection closes or fails.
|
* the connection closes or fails.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2018-2021 Florian Schmaus
|
* Copyright 2018-2022 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -37,6 +37,7 @@ import org.jivesoftware.smack.AbstractXMPPConnection;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.SmackException.NoResponseException;
|
import org.jivesoftware.smack.SmackException.NoResponseException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
|
import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
|
||||||
import org.jivesoftware.smack.SmackFuture;
|
import org.jivesoftware.smack.SmackFuture;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
|
||||||
|
@ -67,7 +68,6 @@ import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Nonza;
|
import org.jivesoftware.smack.packet.Nonza;
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
|
||||||
import org.jivesoftware.smack.packet.StreamError;
|
import org.jivesoftware.smack.packet.StreamError;
|
||||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
import org.jivesoftware.smack.packet.XmlEnvironment;
|
import org.jivesoftware.smack.packet.XmlEnvironment;
|
||||||
|
@ -438,16 +438,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sendStanzaInternal(Stanza stanza) throws NotConnectedException, InterruptedException {
|
protected void sendInternal(TopLevelStreamElement element) throws NotConnectedException, InterruptedException {
|
||||||
sendTopLevelStreamElement(stanza);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendNonza(Nonza nonza) throws NotConnectedException, InterruptedException {
|
|
||||||
sendTopLevelStreamElement(nonza);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendTopLevelStreamElement(TopLevelStreamElement element) throws NotConnectedException, InterruptedException {
|
|
||||||
final XmppClientToServerTransport transport = activeTransport;
|
final XmppClientToServerTransport transport = activeTransport;
|
||||||
if (transport == null) {
|
if (transport == null) {
|
||||||
throw new NotConnectedException();
|
throw new NotConnectedException();
|
||||||
|
@ -457,6 +448,21 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
|
||||||
transport.notifyAboutNewOutgoingElements();
|
transport.notifyAboutNewOutgoingElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sendNonBlockingInternal(TopLevelStreamElement element) throws NotConnectedException, OutgoingQueueFullException {
|
||||||
|
final XmppClientToServerTransport transport = activeTransport;
|
||||||
|
if (transport == null) {
|
||||||
|
throw new NotConnectedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean enqueued = outgoingElementsQueue.offer(element);
|
||||||
|
if (!enqueued) {
|
||||||
|
throw new OutgoingQueueFullException();
|
||||||
|
}
|
||||||
|
|
||||||
|
transport.notifyAboutNewOutgoingElements();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void shutdown() {
|
protected void shutdown() {
|
||||||
shutdown(false);
|
shutdown(false);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2010 Jive Software.
|
* Copyright 2010 Jive Software, 2022 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -23,8 +23,8 @@ import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
|
||||||
import org.jivesoftware.smack.packet.ExtensionElement;
|
import org.jivesoftware.smack.packet.ExtensionElement;
|
||||||
import org.jivesoftware.smack.packet.Nonza;
|
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
|
||||||
|
@ -127,13 +127,16 @@ public class DummyConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendNonza(Nonza element) {
|
protected void sendInternal(TopLevelStreamElement element) {
|
||||||
queue.add(element);
|
queue.add(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sendStanzaInternal(Stanza packet) {
|
protected void sendNonBlockingInternal(TopLevelStreamElement element) throws OutgoingQueueFullException {
|
||||||
queue.add(packet);
|
boolean enqueued = queue.add(element);
|
||||||
|
if (!enqueued) {
|
||||||
|
throw new OutgoingQueueFullException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.logging.Logger;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A threaded dummy connection.
|
* A threaded dummy connection.
|
||||||
|
@ -40,10 +41,11 @@ public class ThreadedDummyConnection extends DummyConnection {
|
||||||
private volatile boolean timeout = false;
|
private volatile boolean timeout = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void sendStanzaInternal(Stanza packet) {
|
protected void sendInternal(TopLevelStreamElement element) {
|
||||||
super.sendStanzaInternal(packet);
|
super.sendInternal(element);
|
||||||
|
|
||||||
if (packet instanceof IQ && !timeout) {
|
if (element instanceof IQ && !timeout) {
|
||||||
|
IQ iq = (IQ) element;
|
||||||
timeout = false;
|
timeout = false;
|
||||||
// Set reply packet to match one being sent. We haven't started the
|
// Set reply packet to match one being sent. We haven't started the
|
||||||
// other thread yet so this is still safe.
|
// other thread yet so this is still safe.
|
||||||
|
@ -51,11 +53,11 @@ public class ThreadedDummyConnection extends DummyConnection {
|
||||||
|
|
||||||
// If no reply has been set via addIQReply, then we create a simple reply
|
// If no reply has been set via addIQReply, then we create a simple reply
|
||||||
if (replyPacket == null) {
|
if (replyPacket == null) {
|
||||||
replyPacket = IQ.createResultIQ((IQ) packet);
|
replyPacket = IQ.createResultIQ(iq);
|
||||||
replyQ.add(replyPacket);
|
replyQ.add(replyPacket);
|
||||||
}
|
}
|
||||||
replyPacket.setStanzaId(packet.getStanzaId());
|
replyPacket.setStanzaId(iq.getStanzaId());
|
||||||
replyPacket.setTo(packet.getFrom());
|
replyPacket.setTo(iq.getFrom());
|
||||||
if (replyPacket.getType() == null) {
|
if (replyPacket.getType() == null) {
|
||||||
replyPacket.setType(IQ.Type.result);
|
replyPacket.setType(IQ.Type.result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ import org.jivesoftware.smack.SmackException.ConnectionException;
|
||||||
import org.jivesoftware.smack.SmackException.EndpointConnectionException;
|
import org.jivesoftware.smack.SmackException.EndpointConnectionException;
|
||||||
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
import org.jivesoftware.smack.SmackException.NotConnectedException;
|
||||||
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
import org.jivesoftware.smack.SmackException.NotLoggedInException;
|
||||||
|
import org.jivesoftware.smack.SmackException.OutgoingQueueFullException;
|
||||||
import org.jivesoftware.smack.SmackException.SecurityNotPossibleException;
|
import org.jivesoftware.smack.SmackException.SecurityNotPossibleException;
|
||||||
import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
|
import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
|
||||||
import org.jivesoftware.smack.SmackFuture;
|
import org.jivesoftware.smack.SmackFuture;
|
||||||
|
@ -79,12 +80,12 @@ import org.jivesoftware.smack.internal.SmackTlsContext;
|
||||||
import org.jivesoftware.smack.packet.Element;
|
import org.jivesoftware.smack.packet.Element;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Nonza;
|
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.packet.Stanza;
|
import org.jivesoftware.smack.packet.Stanza;
|
||||||
import org.jivesoftware.smack.packet.StartTls;
|
import org.jivesoftware.smack.packet.StartTls;
|
||||||
import org.jivesoftware.smack.packet.StreamError;
|
import org.jivesoftware.smack.packet.StreamError;
|
||||||
import org.jivesoftware.smack.packet.StreamOpen;
|
import org.jivesoftware.smack.packet.StreamOpen;
|
||||||
|
import org.jivesoftware.smack.packet.TopLevelStreamElement;
|
||||||
import org.jivesoftware.smack.proxy.ProxyInfo;
|
import org.jivesoftware.smack.proxy.ProxyInfo;
|
||||||
import org.jivesoftware.smack.sasl.packet.SaslNonza;
|
import org.jivesoftware.smack.sasl.packet.SaslNonza;
|
||||||
import org.jivesoftware.smack.sm.SMUtils;
|
import org.jivesoftware.smack.sm.SMUtils;
|
||||||
|
@ -464,7 +465,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (Stanza stanza : previouslyUnackedStanzas) {
|
for (Stanza stanza : previouslyUnackedStanzas) {
|
||||||
sendStanzaInternal(stanza);
|
sendInternal(stanza);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,24 +571,38 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
initState();
|
initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private interface SmAckAction<E extends Exception> {
|
||||||
public void sendNonza(Nonza element) throws NotConnectedException, InterruptedException {
|
void run() throws NotConnectedException, E;
|
||||||
packetWriter.sendStreamElement(element);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private <E extends Exception> void requestSmAckIfNecessary(TopLevelStreamElement element,
|
||||||
protected void sendStanzaInternal(Stanza packet) throws NotConnectedException, InterruptedException {
|
SmAckAction<E> smAckAction) throws NotConnectedException, E {
|
||||||
packetWriter.sendStreamElement(packet);
|
if (!isSmEnabled())
|
||||||
if (isSmEnabled()) {
|
return;
|
||||||
|
|
||||||
|
if (element instanceof Stanza) {
|
||||||
|
Stanza stanza = (Stanza) element;
|
||||||
for (StanzaFilter requestAckPredicate : requestAckPredicates) {
|
for (StanzaFilter requestAckPredicate : requestAckPredicates) {
|
||||||
if (requestAckPredicate.accept(packet)) {
|
if (requestAckPredicate.accept(stanza)) {
|
||||||
requestSmAcknowledgementInternal();
|
smAckAction.run();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sendInternal(TopLevelStreamElement element) throws NotConnectedException, InterruptedException {
|
||||||
|
packetWriter.sendStreamElement(element);
|
||||||
|
requestSmAckIfNecessary(element, () -> requestSmAcknowledgementInternal());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sendNonBlockingInternal(TopLevelStreamElement element) throws NotConnectedException, OutgoingQueueFullException {
|
||||||
|
packetWriter.sendNonBlocking(element);
|
||||||
|
requestSmAckIfNecessary(element, () -> requestSmAcknowledgementNonBlockingInternal());
|
||||||
|
}
|
||||||
|
|
||||||
private void connectUsingConfiguration() throws ConnectionException, IOException, InterruptedException {
|
private void connectUsingConfiguration() throws ConnectionException, IOException, InterruptedException {
|
||||||
RemoteXmppTcpConnectionEndpoints.Result<Rfc6120TcpRemoteConnectionEndpoint> result = RemoteXmppTcpConnectionEndpoints.lookup(config);
|
RemoteXmppTcpConnectionEndpoints.Result<Rfc6120TcpRemoteConnectionEndpoint> result = RemoteXmppTcpConnectionEndpoints.lookup(config);
|
||||||
|
|
||||||
|
@ -1067,7 +1082,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
List<Stanza> stanzasToResend = new ArrayList<>(unacknowledgedStanzas.size());
|
List<Stanza> stanzasToResend = new ArrayList<>(unacknowledgedStanzas.size());
|
||||||
unacknowledgedStanzas.drainTo(stanzasToResend);
|
unacknowledgedStanzas.drainTo(stanzasToResend);
|
||||||
for (Stanza stanza : stanzasToResend) {
|
for (Stanza stanza : stanzasToResend) {
|
||||||
sendStanzaInternal(stanza);
|
XMPPTCPConnection.this.sendInternal(stanza);
|
||||||
}
|
}
|
||||||
// If there where stanzas resent, then request a SM ack for them.
|
// If there where stanzas resent, then request a SM ack for them.
|
||||||
// Writer's sendStreamElement() won't do it automatically based on
|
// Writer's sendStreamElement() won't do it automatically based on
|
||||||
|
@ -1270,6 +1285,22 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the specified element to the server.
|
||||||
|
*
|
||||||
|
* @param element the element to send.
|
||||||
|
* @throws NotConnectedException if the XMPP connection is not connected.
|
||||||
|
* @throws OutgoingQueueFullException if there is no space in the outgoing queue.
|
||||||
|
*/
|
||||||
|
protected void sendNonBlocking(Element element) throws NotConnectedException, OutgoingQueueFullException {
|
||||||
|
throwNotConnectedExceptionIfDoneAndResumptionNotPossible();
|
||||||
|
boolean enqueued = queue.offer(element);
|
||||||
|
if (!enqueued) {
|
||||||
|
throwNotConnectedExceptionIfDoneAndResumptionNotPossible();
|
||||||
|
throw new OutgoingQueueFullException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuts down the stanza writer. Once this method has been called, no further
|
* Shuts down the stanza writer. Once this method has been called, no further
|
||||||
* packets will be written to the server.
|
* packets will be written to the server.
|
||||||
|
@ -1588,6 +1619,10 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
|
||||||
packetWriter.sendStreamElement(AckRequest.INSTANCE);
|
packetWriter.sendStreamElement(AckRequest.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void requestSmAcknowledgementNonBlockingInternal() throws NotConnectedException, OutgoingQueueFullException {
|
||||||
|
packetWriter.sendNonBlocking(AckRequest.INSTANCE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a unconditional Stream Management acknowledgment to the server.
|
* Send a unconditional Stream Management acknowledgment to the server.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
Loading…
Reference in a new issue