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 da961fd85..940e88b11 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
@@ -214,9 +214,24 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
private boolean useSm = useSmDefault;
private boolean useSmResumption = useSmResumptionDefault;
private long serverHandledStanzasCount = 0;
+
+ /**
+ * The counter for stanzas handled ("received") by the client.
+ *
+ * Note that we don't need to synchronize this counter. Although JLS 17.7 states that reads and writes to longs are
+ * not atomic, it guarantees that there are at most 2 separate writes, one to each 32-bit half. And since
+ * {@link SMUtils#incrementHeight(long)} masks the lower 32 bit, we only operate on one half of the long and
+ * therefore have no concurrency problem because the read/write operations on one half are guaranteed to be atomic.
+ *
+ */
private long clientHandledStanzasCount = 0;
private BlockingQueue unacknowledgedStanzas;
+ /**
+ * Set to true if Stream Management was at least once enabled for this connection.
+ */
+ private boolean smWasEnabledAtLeastOnce = false;
+
/**
* This listeners are invoked for every stanza that got acknowledged.
*
@@ -230,7 +245,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
* This listeners are invoked for a acknowledged stanza that has the given stanza ID. They will
* only be invoked once and automatically removed after that.
*/
- private final Map idStanzaAcknowledgedListeners = new ConcurrentHashMap();
+ private final Map stanzaIdAcknowledgedListeners = new ConcurrentHashMap();
/**
* Predicates that determine if an stream management ack should be requested from the server.
@@ -1156,6 +1171,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
smSessionId = null;
}
smEnabledSyncPoint.reportSuccess();
+ smWasEnabledAtLeastOnce = true;
LOGGER.fine("Stream Management (XEP-198): succesfully enabled");
break;
case Failed.ELEMENT:
@@ -1462,19 +1478,47 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
}
+ /**
+ * Set if Stream Management should be used by default for new connections.
+ *
+ * @param useSmDefault true to use Stream Management for new connections.
+ */
public static void setUseStreamManagementDefault(boolean useSmDefault) {
XMPPTCPConnection.useSmDefault = useSmDefault;
}
- public static void setUseStreamManagementResumptiodDefault(boolean useSmResupmptionDefault) {
- XMPPTCPConnection.useSmResumptionDefault = useSmResupmptionDefault;
+ /**
+ * Set if Stream Management resumption should be used by default for new connections.
+ *
+ * @param useSmResumptionDefault true to use Stream Management resumption for new connections.
+ */
+ public static void setUseStreamManagementResumptiodDefault(boolean useSmResumptionDefault) {
+ if (useSmResumptionDefault) {
+ // Also enable SM is resumption is enabled
+ setUseStreamManagementDefault(useSmResumptionDefault);
+ }
+ XMPPTCPConnection.useSmResumptionDefault = useSmResumptionDefault;
}
+ /**
+ * Set if Stream Management should be used if supported by the server.
+ *
+ * @param useSm true to use Stream Management.
+ */
public void setUseStreamManagement(boolean useSm) {
this.useSm = useSm;
}
+ /**
+ * Set if Stream Management resumption should be used if supported by the server.
+ *
+ * @param useSmResumption true to use Stream Management resumption.
+ */
public void setUseStreamManagementResumption(boolean useSmResumption) {
+ if (useSmResumption) {
+ // Also enable SM is resumption is enabled
+ setUseStreamManagement(useSmResumption);
+ }
this.useSmResumption = useSmResumption;
}
@@ -1486,24 +1530,51 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
smClientMaxResumptionTime = resumptionTime;
}
+ /**
+ * Add a predicate for Stream Management acknowledgment requests.
+ *
+ * Those predicates are used to determine when a Stream Management acknowledgement request is send to the server.
+ * Some pre-defined predicates are found in the org.jivesoftware.smack.tcp.sm.predicates
package.
+ *
+ *
+ * If not predicate is configured, the {@link Predicate#forMessagesOrAfter5Stanzas()} will be used.
+ *
+ *
+ * @param predicate the predicate to add.
+ * @return if the predicate was not already active.
+ */
public boolean addRequestAckPredicate(PacketFilter predicate) {
synchronized (requestAckPredicates) {
return requestAckPredicates.add(predicate);
}
}
+ /**
+ * Remove the given predicate for Stream Management acknowledgment request.
+ * @param predicate the predicate to remove.
+ * @return true if the predicate was removed.
+ */
public boolean removeRequestAckPredicate(PacketFilter predicate) {
synchronized (requestAckPredicates) {
return requestAckPredicates.remove(predicate);
}
}
+ /**
+ * Remove all predicates for Stream Management acknowledgment requests.
+ */
public void removeAllRequestAckPredicates() {
synchronized (requestAckPredicates) {
requestAckPredicates.clear();
}
}
+ /**
+ * Send an unconditional Stream Management acknowledgement request to the server.
+ *
+ * @throws StreamManagementNotEnabledException if Stream Mangement is not enabled.
+ * @throws NotConnectedException if the connection is not connected.
+ */
public void requestSmAcknowledgement() throws StreamManagementNotEnabledException, NotConnectedException {
if (!isSmEnabled()) {
throw new StreamManagementException.StreamManagementNotEnabledException();
@@ -1515,6 +1586,17 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
packetWriter.sendStreamElement(AckRequest.INSTANCE);
}
+ /**
+ * Send a unconditional Stream Management acknowledgment to the server.
+ *
+ * See XEP-198: Stream Management ยง 4. Acks:
+ * "Either party MAY send an element at any time (e.g., after it has received a certain number of stanzas,
+ * or after a certain period of time), even if it has not received an element from the other party."
+ *
+ *
+ * @throws StreamManagementNotEnabledException if Stream Management is not enabled.
+ * @throws NotConnectedException if the connection is not connected.
+ */
public void sendSmAcknowledgement() throws StreamManagementNotEnabledException, NotConnectedException {
if (!isSmEnabled()) {
throw new StreamManagementException.StreamManagementNotEnabledException();
@@ -1526,42 +1608,111 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
packetWriter.sendStreamElement(new AckAnswer(clientHandledStanzasCount));
}
- public void addStanzaAcknowledgedListener(PacketListener listener) {
+ /**
+ * Add a Stanza acknowledged listener.
+ *
+ * Those listeners will be invoked every time a Stanza has been acknowledged by the server. The will not get
+ * automatically removed. Consider using {@link #addStanzaIdAcknowledgedListener(String, PacketListener)} when
+ * possible.
+ *
+ *
+ * @param listener the listener to add.
+ * @throws StreamManagementNotEnabledException if Stream Management is not enabled.
+ */
+ public void addStanzaAcknowledgedListener(PacketListener listener) throws StreamManagementNotEnabledException {
+ // Prevent users from adding callbacks that will never get removed
+ if (!smWasEnabledAtLeastOnce) {
+ throw new StreamManagementException.StreamManagementNotEnabledException();
+ }
stanzaAcknowledgedListeners.add(listener);
}
+ /**
+ * Remove the given Stanza acknowledged listener.
+ *
+ * @param listener the listener.
+ * @return true if the listener was removed.
+ */
public boolean removeStanzaAcknowledgedListener(PacketListener listener) {
return stanzaAcknowledgedListeners.remove(listener);
}
+ /**
+ * Remove all stanza acknowledged listeners.
+ */
public void removeAllStanzaAcknowledgedListeners() {
stanzaAcknowledgedListeners.clear();
}
- public PacketListener addIdStanzaAcknowledgedListener(String id, PacketListener listener) {
- return idStanzaAcknowledgedListeners.put(id, listener);
+ /**
+ * Add a new Stanza ID acknowledged listener for the given ID.
+ *
+ * The listener will be invoked if the stanza with the given ID was acknowledged by the server. It will
+ * automatically be removed after the listener was run.
+ *
+ *
+ * @param id the stanza ID.
+ * @param listener the listener to invoke.
+ * @return the previous listener for this stanza ID or null.
+ * @throws StreamManagementNotEnabledException if Stream Management is not enabled.
+ */
+ public PacketListener addStanzaIdAcknowledgedListener(String id, PacketListener listener) throws StreamManagementNotEnabledException {
+ // Prevent users from adding callbacks that will never get removed
+ if (!smWasEnabledAtLeastOnce) {
+ throw new StreamManagementException.StreamManagementNotEnabledException();
+ }
+ return stanzaIdAcknowledgedListeners.put(id, listener);
}
- public PacketListener removeIdStanzaAcknowledgedListener(String id) {
- return idStanzaAcknowledgedListeners.remove(id);
+ /**
+ * Remove the Stanza ID acknowledged listener for the given ID.
+ *
+ * @param id the stanza ID.
+ * @return true if the listener was found and removed, false otherwise.
+ */
+ public PacketListener removeStanzaIdAcknowledgedListener(String id) {
+ return stanzaIdAcknowledgedListeners.remove(id);
}
- public void removeAllIdStanzaAcknowledgedListeners() {
- idStanzaAcknowledgedListeners.clear();
+ /**
+ * Removes all Stanza ID acknowledged listeners.
+ */
+ public void removeAllStanzaIdAcknowledgedListeners() {
+ stanzaIdAcknowledgedListeners.clear();
}
+ /**
+ * Returns true if Stream Management is supported by the server.
+ *
+ * @return true if Stream Management is supported by the server.
+ */
public boolean isSmAvailable() {
return hasFeature(StreamManagementFeature.ELEMENT, StreamManagement.NAMESPACE);
}
+ /**
+ * Returns true if Stream Management was successfully negotiated with the server.
+ *
+ * @return true if Stream Management was negotiated.
+ */
public boolean isSmEnabled() {
return smEnabledSyncPoint.wasSuccessful();
}
+ /**
+ * Returns true if the connection is disconnected by a Stream resumption via Stream Management is possible.
+ *
+ * @return true if disconnected but resumption possible.
+ */
public boolean isDisconnectedButSmResumptionPossible() {
return disconnectedButResumeable && isSmResumptionPossible();
}
+ /**
+ * Returns true if the stream is resumable.
+ *
+ * @return true if the stream is resumable.
+ */
public boolean isSmResumptionPossible() {
// There is no resumable stream available
if (smSessionId == null)
@@ -1603,7 +1754,7 @@ public class XMPPTCPConnection extends AbstractXMPPConnection {
}
String id = ackedStanza.getPacketID();
if (id != null) {
- PacketListener listener = idStanzaAcknowledgedListeners.remove(id);
+ PacketListener listener = stanzaIdAcknowledgedListeners.remove(id);
if (listener != null) {
listener.processPacket(ackedStanza);
}