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

Compare commits

...

30 commits

Author SHA1 Message Date
Florian Schmaus
d74f19e26a Smack 4.4.0-alpha5-SNAPSHOT 2020-06-14 23:00:21 +02:00
Florian Schmaus
b6be7a9694 Smack 4.4.0-alpha4 2020-06-14 22:23:42 +02:00
Florian Schmaus
3f9ca68134 Delete TypedCloneable 2020-06-14 17:38:51 +02:00
Florian Schmaus
18c2c37ad0 Rename XmlUnitUtils to XmlAssertUtil 2020-06-14 16:53:21 +02:00
Florian Schmaus
9d6665735f [pubsub] Rework NodeExtension.toXML() 2020-06-14 16:52:13 +02:00
Florian Schmaus
c689bef7ec Add static QNAME field to Compressed, Failure and Tls(Failure|Proceed) 2020-06-14 16:51:28 +02:00
Florian Schmaus
cf1f533fff [tcp] Mark SM sync points as volatile 2020-06-12 21:16:05 +02:00
Florian Schmaus
fd1b49ca8f [tcp] Set sync points before they are used, and not in init() 2020-06-12 18:15:40 +02:00
Florian Schmaus
843c8dd7fe [tcp] Add missing notifyWaitingThreads() after receiving SM <failed/> 2020-06-12 16:35:32 +02:00
damencho
8e337d7810 [muc] Make providesMucService() use the KNOWN_MUC_SERVICES cache
This reduces the number of disco#info queries on MUC join in some
situations.
2020-06-12 15:54:14 +02:00
Florian Schmaus
f1319d1c8b Move getters from Message to MessageView 2020-06-12 09:17:54 +02:00
Florian Schmaus
6a617af158 Merge branch 'master' of github.com:igniterealtime/Smack 2020-06-04 22:51:17 +02:00
adiaholic
1d49de6b60 Add integration test for MultiUserChat
SMACK-888 has a mention of unreachable code and is now solved
under commit 0f7b7df.
This integration test tests those class entities which were to
be set by previously stated unreachable code.
2020-06-04 22:46:52 +02:00
Florian Schmaus
afbe833a97
Merge pull request #395 from adiaholic/docFix
Correct documentation
2020-06-02 22:07:35 +02:00
Florian Schmaus
ccbc0922ad [core] Fix comment 2020-06-02 10:05:56 +02:00
Florian Schmaus
e2a196fa52 [sinttest] Improve status output 2020-05-31 21:08:14 +02:00
Florian Schmaus
6c84356278 [sinttest] Recycle low-level test connections 2020-05-31 21:01:57 +02:00
Florian Schmaus
dd4cd8cede [sinttest] Disconnect unrecycleable connections 2020-05-31 21:01:32 +02:00
Florian Schmaus
2900cc2274 [sinttest] Disconnect connection in LoginIntegrationTest
So that it will cause an connectionClosedOnError() callback, caused by
an connection-timeout stream error.
2020-05-31 21:00:42 +02:00
Florian Schmaus
7d129d6f6c [tcp] Cleanup handling of stream errors in XMPPTCPConnection
There is no need to notify waiting threads, throwing the stream error
will also notify them. Also settings tlsHandled to true is no longer
necessary.
2020-05-31 19:49:42 +02:00
Florian Schmaus
b7465e8200 [tcp] Do not needlessly wait for closing stream tag 2020-05-31 19:49:42 +02:00
Florian Schmaus
84b7adb764 [tcp] Remove javadoc throws annotation
This method does no longer throw.
2020-05-31 19:49:42 +02:00
Florian Schmaus
22baa74298 [tcp] Remove flush() in writer thread
We will flush the stream after the closing stream tag has been written
anyway. No need to do it here.
2020-05-31 19:49:42 +02:00
Florian Schmaus
81f10b0c5b [core] Synchronize notifyConnectionError()
Synchronize notifyConnectionError() so that only one exception is
handled and remove the ASYNC_BUT_ORDERED usage here. The
ASYNC_BUT_ORDERED was added with 7d2c3ac9f ("Do not call synchronized
methods in reader/writer thread"), but is no longer necessary, since
the Semaphores where replaced with conditions in the previous commit.
2020-05-31 19:49:40 +02:00
Florian Schmaus
57961a8cc1 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.
2020-05-31 19:48:47 +02:00
Aditya Borikar
d639e0bc4c Some more docFix es 2020-05-31 01:10:29 +05:30
Florian Schmaus
b1a4ccfae8 [core] Do not weakly reference "channel selected" callback
Since d65f2c932 ("Bump Error Prone version to 2.3.4 and fix new bug
patterns") the channel selected callback is no longer a final field of
the connection instance, hence it may be come null even if the
connection instance is still strongly referenced. Also the
ConnectionAttemptState class uses simply a lambda as callback, which
is also not strongly referenced otherwise.

The "channel selected" callback was wrapped in weak reference, so that
connection instances could get gc'ed if they are still connected but
the user lost all references to them. In this case, the weak reference
to the connection instance would become 'null' and
selectionKey.cancel() would be called.

This change means that a socket and its selection key of a "leaked"
connected connection instance continues to be part of the reactor. But
this may not be that bad: first, users are expected to manager their
connection instances, and disconnect them before they are
discarded. And secondly, at some point the connection likely will get
disconnected, and in this case, the socket and its selection key will
be removed from the reactor.
2020-05-30 19:45:59 +02:00
Aditya Borikar
223d58527b Use correct class name in docs
XMPPConnectionConfiguration/XMPPTCPConnectionConfiguration
2020-05-28 03:29:37 +05:30
Florian Schmaus
eae8acb856 [gradle] Add 'sinttestAll' task 2020-05-26 09:54:06 +02:00
Florian Schmaus
10c6e5d121 [openpgp] Use default constructor of DummyConnection in unit tests 2020-05-26 09:53:43 +02:00
90 changed files with 841 additions and 1161 deletions

View file

@ -662,6 +662,14 @@ task omemoSignalIntTest {
dependsOn project(':smack-omemo-signal-integration-test').tasks.run
}
task sinttestAll {
description 'Run all of Smack\'s integration tests.'
dependsOn {[
integrationTest,
omemoSignalIntTest,
]}
}
def getGitCommit() {
def dotGit = new File("$projectDir/.git")
if (!dotGit.isDirectory()) return 'non-git build'

View file

@ -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
@ -892,6 +963,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
callConnectionClosedListener();
}
private final Object notifyConnectionErrorMonitor = new Object();
/**
* Sends out a notification that there was an error with the connection
* and closes the connection.
@ -899,41 +972,31 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
* @param exception the exception that causes the connection close event.
*/
protected final void notifyConnectionError(final Exception exception) {
if (!isConnected()) {
LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception,
exception);
return;
}
synchronized (notifyConnectionErrorMonitor) {
if (!isConnected()) {
LOGGER.log(Level.INFO, "Connection was already disconnected when attempting to handle " + exception,
exception);
return;
}
ASYNC_BUT_ORDERED.performAsyncButOrdered(this, () -> {
currentConnectionException = exception;
// Note that we first have to set the current connection exception and notify waiting threads, as one of them
// could hold the instance lock, which we also need later when calling instantShutdown().
setCurrentConnectionExceptionAndNotify(exception);
// Closes the connection temporary. A if the connection supports stream management, then a reconnection is
// possible. Note that a connection listener of e.g. XMPPTCPConnection will drop the SM state in
// case the Exception is a StreamErrorException.
instantShutdown();
for (StanzaCollector collector : collectors) {
collector.notifyConnectionError(exception);
}
SmackWrappedException smackWrappedException = new SmackWrappedException(exception);
tlsHandled.reportGenericFailure(smackWrappedException);
saslFeatureReceived.reportGenericFailure(smackWrappedException);
lastFeaturesReceived.reportGenericFailure(smackWrappedException);
closingStreamReceived.reportFailure(smackWrappedException);
// TODO From XMPPTCPConnection. Was called in Smack 4.3 where notifyConnectionError() was part of
// XMPPTCPConnection. Create delegation method?
// maybeCompressFeaturesReceived.reportGenericFailure(smackWrappedException);
synchronized (AbstractXMPPConnection.this) {
notifyAll();
// Closes the connection temporary. A if the connection supports stream management, then a reconnection is
// possible. Note that a connection listener of e.g. XMPPTCPConnection will drop the SM state in
// case the Exception is a StreamErrorException.
instantShutdown();
}
Async.go(() -> {
// Notify connection listeners of the error.
callConnectionClosedOnErrorListener(exception);
}, AbstractXMPPConnection.this + " callConnectionClosedOnErrorListener()");
});
}
}
/**
@ -947,19 +1010,13 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
protected abstract void shutdown();
protected final boolean waitForClosingStreamTagFromServer() {
Exception exception;
try {
// After we send the closing stream element, check if there was already a
// closing stream element sent by the server or wait with a timeout for a
// closing stream element to be received from the server.
exception = closingStreamReceived.checkIfSuccessOrWait();
} catch (InterruptedException | NoResponseException e) {
exception = e;
waitForConditionOrThrowConnectionException(() -> closingStreamReceived, "closing stream tag from the server");
} catch (InterruptedException | SmackException | XMPPException e) {
LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, e);
return false;
}
if (exception != null) {
LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, exception);
}
return exception == null;
return true;
}
@Override
@ -1817,8 +1874,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// Only proceed with SASL auth if TLS is disabled or if the server doesn't announce it
if (!hasFeature(StartTls.ELEMENT, StartTls.NAMESPACE)
|| config.getSecurityMode() == SecurityMode.disabled) {
tlsHandled.reportSuccess();
saslFeatureReceived.reportSuccess();
tlsHandled = saslFeatureReceived = true;
notifyWaitingThreads();
}
}
@ -1827,9 +1884,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
if (hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
if (!hasFeature(Compress.Feature.ELEMENT, Compress.NAMESPACE)
|| !config.isCompressionEnabled()) {
// This was was last features from the server is either it did not contain
// compression or if we disabled it
lastFeaturesReceived.reportSuccess();
// This where the last stream features from the server, either it did not contain
// compression or we disabled it.
lastFeaturesReceived = true;
notifyWaitingThreads();
}
}
afterFeaturesReceived();

View file

@ -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;
}
}
}

View file

@ -17,7 +17,6 @@
package org.jivesoftware.smack;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
@ -368,13 +367,8 @@ public class SmackReactor {
for (SelectionKey selectionKey : selectedKeys) {
SelectableChannel channel = selectionKey.channel();
SelectionKeyAttachment selectionKeyAttachment = (SelectionKeyAttachment) selectionKey.attachment();
ChannelSelectedCallback channelSelectedCallback = selectionKeyAttachment.weaeklyReferencedChannelSelectedCallback.get();
if (channelSelectedCallback != null) {
channelSelectedCallback.onChannelSelected(channel, selectionKey);
}
else {
selectionKey.cancel();
}
ChannelSelectedCallback channelSelectedCallback = selectionKeyAttachment.channelSelectedCallback;
channelSelectedCallback.onChannelSelected(channel, selectionKey);
}
}
@ -422,11 +416,11 @@ public class SmackReactor {
}
public static final class SelectionKeyAttachment {
private final WeakReference<ChannelSelectedCallback> weaeklyReferencedChannelSelectedCallback;
private final ChannelSelectedCallback channelSelectedCallback;
private final AtomicBoolean reactorThreadRacing = new AtomicBoolean();
private SelectionKeyAttachment(ChannelSelectedCallback channelSelectedCallback) {
this.weaeklyReferencedChannelSelectedCallback = new WeakReference<>(channelSelectedCallback);
this.channelSelectedCallback = channelSelectedCallback;
}
private void setRacing() {

View file

@ -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,
}
}

View file

@ -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();

View file

@ -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();
@ -353,21 +359,19 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
}
/**
* Attempt to enter a state. Note that this method may return <code>null</code> if this state can be safely ignored ignored.
* Attempt to enter a state. Note that this method may return <code>null</code> if this state can be safely ignored.
*
* @param successorStateVertex the successor state vertex.
* @param walkStateGraphContext the "walk state graph" context.
* @return A state transition result or <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);
}
}

View file

@ -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);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright © 2014-2015 Florian Schmaus
* Copyright © 2014-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,12 +16,15 @@
*/
package org.jivesoftware.smack.compress.packet;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.Nonza;
public final class Compressed implements Nonza {
public static final String ELEMENT = "compressed";
public static final String NAMESPACE = Compress.NAMESPACE;
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
public static final Compressed INSTANCE = new Compressed();

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,6 +18,8 @@ package org.jivesoftware.smack.compress.packet;
import java.util.Objects;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.StanzaError;
import org.jivesoftware.smack.util.XmlStringBuilder;
@ -26,6 +28,7 @@ public class Failure implements Nonza {
public static final String ELEMENT = "failure";
public static final String NAMESPACE = Compress.NAMESPACE;
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
public enum CompressFailureError {
setup_failed,

View file

@ -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);

View file

@ -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;

View file

@ -212,7 +212,7 @@ public abstract class StateDescriptor {
protected State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
ModularXmppClientToServerConnection connection = connectionInternal.connection;
try {
// If stateClassConstructor is null here, then you probably forgot to override the the
// If stateClassConstructor is null here, then you probably forgot to override the
// StateDescriptor.constructState() method?
return stateClassConstructor.newInstance(connection, this, connectionInternal);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException

View file

@ -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);

View file

@ -17,20 +17,16 @@
package org.jivesoftware.smack.packet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.util.EqualsUtil;
import org.jivesoftware.smack.util.HashCode;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.TypedCloneable;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jxmpp.jid.Jid;
@ -62,7 +58,7 @@ import org.jxmpp.stringprep.XmppStringprepException;
* @author Matt Tucker
*/
public final class Message extends MessageOrPresence<MessageBuilder>
implements MessageView, TypedCloneable<Message> {
implements MessageView {
public static final String ELEMENT = "message";
public static final String BODY = "body";
@ -186,59 +182,6 @@ public final class Message extends MessageOrPresence<MessageBuilder>
this.type = type;
}
/**
* Returns the default subject of the message, or null if the subject has not been set.
* The subject is a short description of message contents.
* <p>
* The default subject of a message is the subject that corresponds to the message's language.
* (see {@link #getLanguage()}) or if no language is set to the applications default
* language (see {@link Stanza#getDefaultLanguage()}).
*
* @return the subject of the message.
*/
public String getSubject() {
return getSubject(null);
}
/**
* Returns the subject corresponding to the language. If the language is null, the method result
* will be the same as {@link #getSubject()}. Null will be returned if the language does not have
* a corresponding subject.
*
* @param language the language of the subject to return.
* @return the subject related to the passed in language.
*/
public String getSubject(String language) {
Subject subject = getMessageSubject(language);
return subject == null ? null : subject.subject;
}
private Subject getMessageSubject(String language) {
language = determineLanguage(language);
for (Subject subject : getSubjects()) {
if (Objects.equals(language, subject.language)
|| (subject.language == null && Objects.equals(this.language, language))) {
return subject;
}
}
return null;
}
/**
* Returns a set of all subjects in this Message, including the default message subject accessible
* from {@link #getSubject()}.
*
* @return a collection of all subjects in this message.
*/
public Set<Subject> getSubjects() {
List<Subject> subjectList = getExtensions(Subject.class);
Set<Subject> subjects = new HashSet<>(subjectList.size());
subjects.addAll(subjectList);
return subjects;
}
/**
* Sets the subject of the message. The subject is a short description of
* message contents.
@ -267,7 +210,7 @@ public final class Message extends MessageOrPresence<MessageBuilder>
@Deprecated
// TODO: Remove when stanza builder is ready.
public Subject addSubject(String language, String subject) {
language = determineLanguage(language);
language = Stanza.determineLanguage(this, language);
List<Subject> currentSubjects = getExtensions(Subject.class);
for (Subject currentSubject : currentSubjects) {
@ -290,7 +233,7 @@ public final class Message extends MessageOrPresence<MessageBuilder>
@Deprecated
// TODO: Remove when stanza builder is ready.
public boolean removeSubject(String language) {
language = determineLanguage(language);
language = Stanza.determineLanguage(this, language);
for (Subject subject : getExtensions(Subject.class)) {
if (language.equals(subject.language)) {
return removeSubject(subject);
@ -311,77 +254,6 @@ public final class Message extends MessageOrPresence<MessageBuilder>
return removeExtension(subject) != null;
}
/**
* Returns all the languages being used for the subjects, not including the default subject.
*
* @return the languages being used for the subjects.
*/
public List<String> getSubjectLanguages() {
Subject defaultSubject = getMessageSubject(null);
List<String> languages = new ArrayList<String>();
for (Subject subject : getExtensions(Subject.class)) {
if (!subject.equals(defaultSubject)) {
languages.add(subject.language);
}
}
return Collections.unmodifiableList(languages);
}
/**
* Returns the default body of the message, or null if the body has not been set. The body
* is the main message contents.
* <p>
* The default body of a message is the body that corresponds to the message's language.
* (see {@link #getLanguage()}) or if no language is set to the applications default
* language (see {@link Stanza#getDefaultLanguage()}).
*
* @return the body of the message.
*/
public String getBody() {
return getBody(language);
}
/**
* Returns the body corresponding to the language. If the language is null, the method result
* will be the same as {@link #getBody()}. Null will be returned if the language does not have
* a corresponding body.
*
* @param language the language of the body to return.
* @return the body related to the passed in language.
* @since 3.0.2
*/
public String getBody(String language) {
Body body = getMessageBody(language);
return body == null ? null : body.message;
}
private Body getMessageBody(String language) {
language = determineLanguage(language);
for (Body body : getBodies()) {
if (Objects.equals(language, body.language) || (language != null && language.equals(this.language) && body.language == null)) {
return body;
}
}
return null;
}
/**
* Returns a set of all bodies in this Message, including the default message body accessible
* from {@link #getBody()}.
*
* @return a collection of all bodies in this Message.
* @since 3.0.2
*/
public Set<Body> getBodies() {
List<ExtensionElement> bodiesList = getExtensions(Body.ELEMENT, Body.NAMESPACE);
Set<Body> resultSet = new HashSet<>(bodiesList.size());
for (ExtensionElement extensionElement : bodiesList) {
Body body = (Body) extensionElement;
resultSet.add(body);
}
return resultSet;
}
/**
* Sets the body of the message.
*
@ -431,7 +303,7 @@ public final class Message extends MessageOrPresence<MessageBuilder>
@Deprecated
// TODO: Remove when stanza builder is ready.
public Body addBody(String language, String body) {
language = determineLanguage(language);
language = Stanza.determineLanguage(this, language);
removeBody(language);
@ -450,7 +322,7 @@ public final class Message extends MessageOrPresence<MessageBuilder>
@Deprecated
// TODO: Remove when stanza builder is ready.
public boolean removeBody(String language) {
language = determineLanguage(language);
language = Stanza.determineLanguage(this, language);
for (Body body : getBodies()) {
String bodyLanguage = body.getLanguage();
if (Objects.equals(bodyLanguage, language)) {
@ -476,37 +348,6 @@ public final class Message extends MessageOrPresence<MessageBuilder>
return removedElement != null;
}
/**
* Returns all the languages being used for the bodies, not including the default body.
*
* @return the languages being used for the bodies.
* @since 3.0.2
*/
public List<String> getBodyLanguages() {
Body defaultBody = getMessageBody(null);
List<String> languages = new ArrayList<String>();
for (Body body : getBodies()) {
if (!body.equals(defaultBody)) {
languages.add(body.language);
}
}
return Collections.unmodifiableList(languages);
}
/**
* Returns the thread id of the message, which is a unique identifier for a sequence
* of "chat" messages. If no thread id is set, <code>null</code> will be returned.
*
* @return the thread id of the message, or <code>null</code> if it doesn't exist.
*/
public String getThread() {
Message.Thread thread = getExtension(Message.Thread.class);
if (thread == null) {
return null;
}
return thread.getThread();
}
/**
* Sets the thread id of the message, which is a unique identifier for a sequence
* of "chat" messages.
@ -520,18 +361,6 @@ public final class Message extends MessageOrPresence<MessageBuilder>
addExtension(new Message.Thread(thread));
}
private String determineLanguage(String language) {
// empty string is passed by #setSubject() and #setBody() and is the same as null
language = "".equals(language) ? null : language;
// if given language is null check if message language is set
if (language == null && this.language != null) {
return this.language;
}
return language;
}
@Override
public String getElementName() {
return ELEMENT;
@ -542,6 +371,16 @@ public final class Message extends MessageOrPresence<MessageBuilder>
return StanzaBuilder.buildMessageFrom(this, getStanzaId());
}
@Override
public MessageBuilder asBuilder(String id) {
return StanzaBuilder.buildMessageFrom(this, id);
}
@Override
public MessageBuilder asBuilder(XMPPConnection connection) {
return connection.getStanzaFactory().buildMessageStanzaFrom(this);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@ -580,7 +419,10 @@ public final class Message extends MessageOrPresence<MessageBuilder>
* instance.
* </p>
* @return a clone of this message.
* @deprecated use {@link #asBuilder()} instead.
*/
// TODO: Remove in Smack 4.5.
@Deprecated
@Override
public Message clone() {
return new Message(this);

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2019 Florian Schmaus
* Copyright 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +16,8 @@
*/
package org.jivesoftware.smack.packet;
import org.jivesoftware.smack.XMPPConnection;
public abstract class MessageOrPresence<MPB extends MessageOrPresenceBuilder<?, ?>> extends Stanza {
@Deprecated
@ -33,4 +35,8 @@ public abstract class MessageOrPresence<MPB extends MessageOrPresenceBuilder<?,
public abstract MPB asBuilder();
public abstract MPB asBuilder(String id);
public abstract MPB asBuilder(XMPPConnection connection);
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2019 Florian Schmaus
* Copyright 2003-2007 Jive Software, 2019-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +16,15 @@
*/
package org.jivesoftware.smack.packet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jivesoftware.smack.packet.Message.Subject;
import org.jivesoftware.smack.util.Objects;
public interface MessageView extends StanzaView {
/**
@ -26,4 +35,158 @@ public interface MessageView extends StanzaView {
*/
Message.Type getType();
/**
* Returns the default subject of the message, or null if the subject has not been set.
* The subject is a short description of message contents.
* <p>
* The default subject of a message is the subject that corresponds to the message's language.
* (see {@link #getLanguage()}) or if no language is set to the applications default
* language (see {@link Stanza#getDefaultLanguage()}).
*
* @return the subject of the message.
*/
default String getSubject() {
return getSubject(null);
}
/**
* Returns the subject corresponding to the language. If the language is null, the method result
* will be the same as {@link #getSubject()}. Null will be returned if the language does not have
* a corresponding subject.
*
* @param language the language of the subject to return.
* @return the subject related to the passed in language.
*/
default String getSubject(String language) {
Subject subject = getMessageSubject(language);
return subject == null ? null : subject.getSubject();
}
default Message.Subject getMessageSubject(String language) {
language = Stanza.determineLanguage(this, language);
for (Message.Subject subject : getSubjects()) {
if (Objects.equals(language, subject.getLanguage())
|| (subject.getLanguage() == null && Objects.equals(getLanguage(), language))) {
return subject;
}
}
return null;
}
/**
* Returns a set of all subjects in this Message, including the default message subject accessible
* from {@link #getSubject()}.
*
* @return a collection of all subjects in this message.
*/
default Set<Message.Subject> getSubjects() {
List<Message.Subject> subjectList = getExtensions(Subject.class);
Set<Message.Subject> subjects = new HashSet<>(subjectList.size());
subjects.addAll(subjectList);
return subjects;
}
/**
* Returns all the languages being used for the subjects, not including the default subject.
*
* @return the languages being used for the subjects.
*/
default List<String> getSubjectLanguages() {
Message.Subject defaultSubject = getMessageSubject(null);
List<String> languages = new ArrayList<String>();
for (Message.Subject subject : getExtensions(Message.Subject.class)) {
if (!subject.equals(defaultSubject)) {
languages.add(subject.getLanguage());
}
}
return Collections.unmodifiableList(languages);
}
/**
* Returns the default body of the message, or null if the body has not been set. The body
* is the main message contents.
* <p>
* The default body of a message is the body that corresponds to the message's language.
* (see {@link #getLanguage()}) or if no language is set to the applications default
* language (see {@link Stanza#getDefaultLanguage()}).
*
* @return the body of the message.
*/
default String getBody() {
return getBody(getLanguage());
}
/**
* Returns the body corresponding to the language. If the language is null, the method result
* will be the same as {@link #getBody()}. Null will be returned if the language does not have
* a corresponding body.
*
* @param language the language of the body to return.
* @return the body related to the passed in language.
* @since 3.0.2
*/
default String getBody(String language) {
Message.Body body = getMessageBody(language);
return body == null ? null : body.getMessage();
}
default Message.Body getMessageBody(String language) {
language = Stanza.determineLanguage(this, language);
for (Message.Body body : getBodies()) {
if (Objects.equals(language, body.getLanguage()) || (language != null && language.equals(getLanguage()) && body.getLanguage() == null)) {
return body;
}
}
return null;
}
/**
* Returns a set of all bodies in this Message, including the default message body accessible
* from {@link #getBody()}.
*
* @return a collection of all bodies in this Message.
* @since 3.0.2
*/
default Set<Message.Body> getBodies() {
List<ExtensionElement> bodiesList = getExtensions(Message.Body.QNAME);
Set<Message.Body> resultSet = new HashSet<>(bodiesList.size());
for (ExtensionElement extensionElement : bodiesList) {
Message.Body body = (Message.Body) extensionElement;
resultSet.add(body);
}
return resultSet;
}
/**
* Returns all the languages being used for the bodies, not including the default body.
*
* @return the languages being used for the bodies.
* @since 3.0.2
*/
default List<String> getBodyLanguages() {
Message.Body defaultBody = getMessageBody(null);
List<String> languages = new ArrayList<String>();
for (Message.Body body : getBodies()) {
if (!body.equals(defaultBody)) {
languages.add(body.getLanguage());
}
}
return Collections.unmodifiableList(languages);
}
/**
* Returns the thread id of the message, which is a unique identifier for a sequence
* of "chat" messages. If no thread id is set, <code>null</code> will be returned.
*
* @return the thread id of the message, or <code>null</code> if it doesn't exist.
*/
default String getThread() {
Message.Thread thread = getExtension(Message.Thread.class);
if (thread == null) {
return null;
}
return thread.getThread();
}
}

View file

@ -20,9 +20,9 @@ package org.jivesoftware.smack.packet;
import java.util.List;
import java.util.Locale;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.TypedCloneable;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jxmpp.jid.Jid;
@ -61,7 +61,7 @@ import org.jxmpp.jid.Jid;
* @author Matt Tucker
*/
public final class Presence extends MessageOrPresence<PresenceBuilder>
implements PresenceView, TypedCloneable<Presence> {
implements PresenceView {
public static final String ELEMENT = "presence";
@ -282,6 +282,16 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
return StanzaBuilder.buildPresenceFrom(this, getStanzaId());
}
@Override
public PresenceBuilder asBuilder(String id) {
return StanzaBuilder.buildPresenceFrom(this, id);
}
@Override
public PresenceBuilder asBuilder(XMPPConnection connection) {
return connection.getStanzaFactory().buildPresenceStanzaFrom(this);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@ -343,7 +353,10 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
* instance.
* </p>
* @return a clone of this presence.
* @deprecated use {@link #asBuilder()} instead.
*/
// TODO: Remove in Smack 4.5.
@Deprecated
@Override
public Presence clone() {
return new Presence(this);
@ -354,7 +367,10 @@ public final class Presence extends MessageOrPresence<PresenceBuilder>
*
* @return a "clone" of this presence with a different stanza ID.
* @since 4.1.2
* @deprecated use {@link #asBuilder(XMPPConnection)} or {@link #asBuilder(String)}instead.
*/
// TODO: Remove in Smack 4.5.
@Deprecated
public Presence cloneWithNewId() {
Presence clone = clone();
clone.setNewStanzaId();

View file

@ -575,4 +575,18 @@ public abstract class Stanza implements StanzaView, TopLevelStreamElement {
xml.append(error);
}
}
/**
* Return the provided non-empty language, or use this {@link XmlLangElement} language (if set).
*
* @param language the provided language, may be the empty string or <code>null</code>.
* @return the provided language or this element's language (if set).
*/
static String determineLanguage(XmlLangElement xmlLangElement, String language) {
if (language != null && !language.isEmpty()) {
return language;
}
return xmlLangElement.getLanguage();
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,12 +16,15 @@
*/
package org.jivesoftware.smack.packet;
import javax.xml.namespace.QName;
public final class TlsFailure implements Nonza {
public static final TlsFailure INSTANCE = new TlsFailure();
public static final String ELEMENT = "failure";
public static final String NAMESPACE = TlsProceed.NAMESPACE;
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private TlsFailure() {
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2018 Florian Schmaus
* Copyright 2018-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,12 +16,15 @@
*/
package org.jivesoftware.smack.packet;
import javax.xml.namespace.QName;
public final class TlsProceed implements Nonza {
public static final TlsProceed INSTANCE = new TlsProceed();
public static final String ELEMENT = "proceed";
public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls";
public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
private TlsProceed() {
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright © 2015-2019 Florian Schmaus
* Copyright © 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -34,7 +34,7 @@ import java.util.Set;
* @param <K> the type of the keys the map uses.
* @param <V> the type of the values the map uses.
*/
public class MultiMap<K, V> implements TypedCloneable<MultiMap<K, V>> {
public class MultiMap<K, V> {
/**
* The constant value {@value}.

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015 Florian Schmaus
* Copyright 2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,19 +16,9 @@
*/
package org.jivesoftware.smack.util;
/**
* An extended version of {@link java.lang.Cloneable}, which defines a generic {@link #clone()}
* method.
*
* @param <T> the type returned by {@link #clone()}.
*/
public interface TypedCloneable<T> extends Cloneable {
// TODO: Replace with java.util.function.Supplier once Smack's minimum Android SDK level is 24 or higher.
public interface Supplier<T> {
/**
* Clone this instance.
*
* @return a cloned version of this instance.
*/
T clone();
T get();
}

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smack.compress.packet;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import java.io.IOException;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smack.packet;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smack.packet;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smack.provider;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import java.io.IOException;

View file

@ -16,8 +16,8 @@
*/
package org.jivesoftware.smack.util;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlNotSimilar;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlNotSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;

View file

@ -18,7 +18,7 @@ package org.jivesoftware.smack.util;
import org.jivesoftware.smack.packet.StandardExtensionElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.test.util.XmlUnitUtils;
import org.jivesoftware.smack.test.util.XmlAssertUtil;
import org.junit.jupiter.api.Test;
@ -38,10 +38,10 @@ public class XmlStringBuilderTest {
String expectedXml = "<outer xmlns='outer-namespace'><inner xmlns='inner-namespace'></inner><inner xmlns='inner-namespace'></inner></outer>";
XmlStringBuilder actualXml = outer.toXML(XmlEnvironment.EMPTY);
XmlUnitUtils.assertXmlSimilar(expectedXml, actualXml);
XmlAssertUtil.assertXmlSimilar(expectedXml, actualXml);
StringBuilder actualXmlTwo = actualXml.toXML(XmlEnvironment.EMPTY);
XmlUnitUtils.assertXmlSimilar(expectedXml, actualXmlTwo);
XmlAssertUtil.assertXmlSimilar(expectedXml, actualXmlTwo);
}
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2014-2019 Florian Schmaus
* Copyright 2014-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -26,8 +26,7 @@ import org.xmlunit.diff.DefaultNodeMatcher;
import org.xmlunit.diff.ElementSelectors;
import org.xmlunit.input.NormalizedSource;
// TODO: Rename this class to XmlAssertUtil
public class XmlUnitUtils {
public class XmlAssertUtil {
public static void assertXmlNotSimilar(CharSequence xmlOne, CharSequence xmlTwo) {
normalizedCompare(xmlOne, xmlTwo).areNotSimilar();

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.httpfileupload;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.httpfileupload;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.httpfileupload.provider;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.net.MalformedURLException;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.mam;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.jivesoftware.smack.packet.StreamOpen;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.message_fastening;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.message_markup;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.message_retraction.element;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.io.IOException;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.message_retraction.element;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.io.IOException;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.reference;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.sid;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.spoiler;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

View file

@ -962,8 +962,9 @@ public final class ServiceDiscoveryManager extends Manager {
// to respect ConnectionConfiguration.isSendPresence()
final Presence presenceSend = this.presenceSend;
if (connection.isAuthenticated() && presenceSend != null) {
Presence presence = presenceSend.asBuilder(connection).build();
try {
connection.sendStanza(presenceSend.cloneWithNewId());
connection.sendStanza(presence);
}
catch (InterruptedException | NotConnectedException e) {
LOGGER.log(Level.WARNING, "Could could not update presence with caps info", e);

View file

@ -30,7 +30,6 @@ import org.jivesoftware.smack.packet.IqData;
import org.jivesoftware.smack.util.EqualsUtil;
import org.jivesoftware.smack.util.HashCode;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.TypedCloneable;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jxmpp.util.XmppStringUtils;
@ -44,7 +43,7 @@ import org.jxmpp.util.XmppStringUtils;
*
* @author Gaston Dombiak
*/
public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable<DiscoverInfo> {
public class DiscoverInfo extends IQ implements DiscoverInfoView {
public static final String ELEMENT = QUERY_ELEMENT;
public static final String NAMESPACE = "http://jabber.org/protocol/disco#info";
@ -303,7 +302,13 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable
return new DiscoverInfoBuilder(this, stanzaId);
}
// TODO: Deprecate in favor of asBuilder().
/**
* Deprecated, do not use.
*
* @deprecated use {@link #asBuilder(String)} instead.
*/
// TODO: Remove in Smack 4.5.
@Deprecated
@Override
public DiscoverInfo clone() {
return new DiscoverInfo(this);
@ -516,7 +521,7 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable
* as well as specific feature types of interest, if any (e.g., for the purpose of feature
* negotiation).
*/
public static final class Feature implements TypedCloneable<Feature> {
public static final class Feature {
private final String variable;
@ -566,11 +571,6 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView, TypedCloneable
return variable.hashCode();
}
@Override
public Feature clone() {
return new Feature(this);
}
@Override
public String toString() {
return toXML().toString();

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2015-2016 Florian Schmaus
* Copyright 2015-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -60,15 +60,17 @@ public final class MucEnterConfiguration {
since = builder.since;
timeout = builder.timeout;
final PresenceBuilder joinPresenceBuilder;
if (builder.joinPresence == null) {
joinPresence = builder.joinPresenceBuilder.ofType(Presence.Type.available).build();
joinPresenceBuilder = builder.joinPresenceBuilder.ofType(Presence.Type.available);
}
else {
joinPresence = builder.joinPresence.clone();
joinPresenceBuilder = builder.joinPresence.asBuilder();
}
// Indicate the the client supports MUC
joinPresence.addExtension(new MUCInitialPresence(password, maxChars, maxStanzas, seconds,
joinPresenceBuilder.addExtension(new MUCInitialPresence(password, maxChars, maxStanzas, seconds,
since));
joinPresence = joinPresenceBuilder.build();
}
Presence getJoinPresence(MultiUserChat multiUserChat) {
@ -92,6 +94,8 @@ public final class MucEnterConfiguration {
private long timeout;
private final PresenceBuilder joinPresenceBuilder;
// TODO: Remove in Smack 4.5.
private Presence joinPresence;
Builder(Resourcepart nickname, XMPPConnection connection) {

View file

@ -88,7 +88,6 @@ import org.jxmpp.jid.EntityJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart;
import org.jxmpp.util.cache.ExpirationCache;
/**
* A MultiUserChat room (XEP-45), created with {@link MultiUserChatManager#getMultiUserChat(EntityBareJid)}.
@ -112,9 +111,6 @@ import org.jxmpp.util.cache.ExpirationCache;
public class MultiUserChat {
private static final Logger LOGGER = Logger.getLogger(MultiUserChat.class.getName());
private static final ExpirationCache<DomainBareJid, Void> KNOWN_MUC_SERVICES = new ExpirationCache<>(
100, 1000 * 60 * 60 * 24);
private final XMPPConnection connection;
private final EntityBareJid room;
private final MultiUserChatManager multiUserChatManager;
@ -340,12 +336,8 @@ public class MultiUserChat {
private Presence enter(MucEnterConfiguration conf) throws NotConnectedException, NoResponseException,
XMPPErrorException, InterruptedException, NotAMucServiceException {
final DomainBareJid mucService = room.asDomainBareJid();
if (!KNOWN_MUC_SERVICES.containsKey(mucService)) {
if (multiUserChatManager.providesMucService(mucService)) {
KNOWN_MUC_SERVICES.put(mucService, null);
} else {
throw new NotAMucServiceException(this);
}
if (!multiUserChatManager.providesMucService(mucService)) {
throw new NotAMucServiceException(this);
}
// We enter a room by sending a presence packet where the "to"
// field is in the form "roomName@service/nickname"

View file

@ -64,6 +64,7 @@ import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.EntityJid;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.parts.Resourcepart;
import org.jxmpp.util.cache.ExpirationCache;
/**
* A manager for Multi-User Chat rooms.
@ -136,6 +137,9 @@ public final class MultiUserChatManager extends Manager {
private static final StanzaFilter INVITATION_FILTER = new AndFilter(StanzaTypeFilter.MESSAGE, new StanzaExtensionFilter(new MUCUser()),
new NotFilter(MessageTypeFilter.ERROR));
private static final ExpirationCache<DomainBareJid, Void> KNOWN_MUC_SERVICES = new ExpirationCache<>(
100, 1000 * 60 * 60 * 24);
private final Set<InvitationListener> invitationsListeners = new CopyOnWriteArraySet<InvitationListener>();
/**
@ -392,8 +396,16 @@ public final class MultiUserChatManager extends Manager {
*/
public boolean providesMucService(DomainBareJid domainBareJid) throws NoResponseException,
XMPPErrorException, NotConnectedException, InterruptedException {
return serviceDiscoveryManager.supportsFeature(domainBareJid,
MUCInitialPresence.NAMESPACE);
boolean contains = KNOWN_MUC_SERVICES.containsKey(domainBareJid);
if (!contains) {
if (serviceDiscoveryManager.supportsFeature(domainBareJid,
MUCInitialPresence.NAMESPACE)) {
KNOWN_MUC_SERVICES.put(domainBareJid, null);
return true;
}
}
return contains;
}
/**

View file

@ -32,7 +32,6 @@ import org.jivesoftware.smackx.pubsub.Affiliation.AffiliationNamespace;
*/
public class AffiliationsExtension extends NodeExtension {
protected List<Affiliation> items = Collections.emptyList();
private final String node;
public AffiliationsExtension() {
this(null);
@ -51,9 +50,8 @@ public class AffiliationsExtension extends NodeExtension {
}
public AffiliationsExtension(AffiliationNamespace affiliationsNamespace, List<Affiliation> subList, String node) {
super(affiliationsNamespace.type);
super(affiliationsNamespace.type, node);
items = subList;
this.node = node;
}
public List<Affiliation> getAffiliations() {
@ -61,19 +59,14 @@ public class AffiliationsExtension extends NodeExtension {
}
@Override
public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
protected void addXml(XmlStringBuilder xml) {
if ((items == null) || (items.size() == 0)) {
return super.toXML(enclosingNamespace);
}
else {
// Can't use XmlStringBuilder(this), because we don't want the namespace to be included
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(getElementName());
xml.optAttribute("node", node);
xml.rightAngleBracket();
xml.append(items);
xml.closeElement(this);
return xml;
xml.closeEmptyElement();
return;
}
xml.rightAngleBracket();
xml.append(items);
xml.closeElement(this);
}
}

View file

@ -16,6 +16,8 @@
*/
package org.jivesoftware.smackx.pubsub;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.xdata.packet.DataForm;
/**
@ -69,26 +71,14 @@ public class FormNode extends NodeExtension {
}
@Override
public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
protected void addXml(XmlStringBuilder xml) {
if (configForm == null) {
return super.toXML(enclosingNamespace);
xml.closeEmptyElement();
return;
}
else {
StringBuilder builder = new StringBuilder("<");
builder.append(getElementName());
if (getNode() != null) {
builder.append(" node='");
builder.append(getNode());
builder.append("'>");
}
else
builder.append('>');
builder.append(configForm.toXML());
builder.append("</");
builder.append(getElementName() + '>');
return builder.toString();
}
xml.append(configForm);
xml.closeElement(this);
}
}

View file

@ -54,13 +54,9 @@ public class GetItemsRequest extends NodeExtension {
}
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(getElementName());
xml.attribute("node", getNode());
protected void addXml(XmlStringBuilder xml) {
xml.optAttribute("subid", getSubscriptionId());
xml.optIntAttribute("max_items", getMaxItems());
xml.closeEmptyElement();
return xml;
}
}

View file

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

View file

@ -20,6 +20,7 @@ import java.util.List;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.NamedElement;
import org.jivesoftware.smack.util.XmlStringBuilder;
/**
* This class is used for multiple purposes.
@ -150,35 +151,21 @@ public class ItemsExtension extends NodeExtension implements EmbeddedPacketExten
}
@Override
public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
protected void addXml(XmlStringBuilder xml) {
if ((items == null) || (items.size() == 0)) {
return super.toXML(enclosingNamespace);
xml.closeEmptyElement();
return;
}
else {
StringBuilder builder = new StringBuilder("<");
builder.append(getElementName());
builder.append(" node='");
builder.append(getNode());
if (notify != null) {
builder.append("' ");
builder.append(type.getElementAttribute());
builder.append("='");
builder.append(notify.equals(Boolean.TRUE) ? 1 : 0);
builder.append("'>");
}
else {
builder.append("'>");
for (NamedElement item : items) {
builder.append(item.toXML());
}
}
builder.append("</");
builder.append(getElementName());
builder.append('>');
return builder.toString();
if (notify != null) {
xml.attribute(type.getElementAttribute(), notify);
xml.rightAngleBracket();
} else {
xml.rightAngleBracket();
xml.append(items);
}
xml.closeElement(this);
}
@Override

View file

@ -17,6 +17,8 @@
package org.jivesoftware.smackx.pubsub;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
@ -78,8 +80,17 @@ public class NodeExtension implements ExtensionElement {
}
@Override
public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
return '<' + getElementName() + (node == null ? "" : " node='" + node + '\'') + "/>";
public final XmlStringBuilder toXML(XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
xml.optAttribute("node", node);
addXml(xml);
return xml;
}
protected void addXml(XmlStringBuilder xml) {
xml.closeEmptyElement();
}
@Override

View file

@ -50,13 +50,15 @@ public class OptionsExtension extends NodeExtension {
}
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder();
protected void addXml(XmlStringBuilder xml) {
xml.rightAngleBracket();
xml.halfOpenElement(getElementName());
xml.attribute("jid", jid);
xml.optAttribute("node", getNode());
xml.optAttribute("subid", id);
xml.closeEmptyElement();
return xml;
xml.closeElement(this);
}
}

View file

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

View file

@ -19,6 +19,8 @@ package org.jivesoftware.smackx.pubsub;
import java.util.ArrayList;
import java.util.Collection;
import org.jivesoftware.smack.util.XmlStringBuilder;
/**
* Represents a request to publish an item(s) to a specific node.
*
@ -51,18 +53,9 @@ public class PublishItem<T extends Item> extends NodeExtension {
}
@Override
public String toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
StringBuilder builder = new StringBuilder("<");
builder.append(getElementName());
builder.append(" node='");
builder.append(getNode());
builder.append("'>");
for (Item item : items) {
builder.append(item.toXML());
}
builder.append("</publish>");
return builder.toString();
protected void addXml(XmlStringBuilder xml) {
xml.rightAngleBracket();
xml.append(items);
xml.closeElement(this);
}
}

View file

@ -16,7 +16,6 @@
*/
package org.jivesoftware.smackx.pubsub;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.XmlStringBuilder;
import org.jxmpp.jid.Jid;
@ -44,11 +43,8 @@ public class SubscribeExtension extends NodeExtension {
}
@Override
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
xml.optAttribute("node", getNode());
protected void addXml(XmlStringBuilder xml) {
xml.attribute("jid", getJid());
xml.closeEmptyElement();
return xml;
}
}

View file

@ -138,16 +138,11 @@ public class Subscription extends NodeExtension {
}
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder builder = new XmlStringBuilder(this);
builder.attribute("jid", jid);
builder.optAttribute("node", getNode());
builder.optAttribute("subid", id);
builder.optAttribute("subscription", state.toString());
builder.closeEmptyElement();
return builder;
protected void addXml(XmlStringBuilder xml) {
xml.attribute("jid", jid);
xml.optAttribute("subid", id);
xml.optAttribute("subscription", state);
xml.closeEmptyElement();
}
}

View file

@ -19,6 +19,8 @@ package org.jivesoftware.smackx.pubsub;
import java.util.Collections;
import java.util.List;
import org.jivesoftware.smack.util.XmlStringBuilder;
/**
* Represents the element holding the list of subscription elements.
*
@ -91,29 +93,13 @@ public class SubscriptionsExtension extends NodeExtension {
}
@Override
public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
protected void addXml(XmlStringBuilder xml) {
if ((items == null) || (items.size() == 0)) {
return super.toXML(enclosingNamespace);
}
else {
StringBuilder builder = new StringBuilder("<");
builder.append(getElementName());
if (getNode() != null) {
builder.append(" node='");
builder.append(getNode());
builder.append('\'');
}
builder.append('>');
for (Subscription item : items) {
builder.append(item.toXML());
}
builder.append("</");
builder.append(getElementName());
builder.append('>');
return builder.toString();
xml.closeEmptyElement();
return;
}
xml.rightAngleBracket();
xml.append(items);
xml.closeElement(this);
}
}

View file

@ -51,13 +51,9 @@ public class UnsubscribeExtension extends NodeExtension {
}
@Override
public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
XmlStringBuilder xml = new XmlStringBuilder();
xml.halfOpenElement(getElementName());
protected void addXml(XmlStringBuilder xml) {
xml.attribute("jid", jid);
xml.optAttribute("node", getNode());
xml.optAttribute("subid", id);
xml.closeEmptyElement();
return xml;
}
}

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.attention;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import org.jivesoftware.smackx.attention.packet.AttentionExtension;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.bytestreams.ibb.packet;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.bytestreams.ibb.packet;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.bytestreams.ibb.packet;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.bytestreams.ibb.packet;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.last_interaction;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.mood;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.mood;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.nick;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2017 Florian Schmaus
* Copyright 2017-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,13 +16,14 @@
*/
package org.jivesoftware.smackx.pubsub;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.smackx.pubsub.Affiliation.Type;
import org.jivesoftware.smackx.pubsub.packet.PubSub;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.BareJid;
@ -40,10 +41,10 @@ public class AffiliationsExtensionTest {
AffiliationsExtension affiliationsExtension = new AffiliationsExtension(affiliationsList, "testNode");
CharSequence xml = affiliationsExtension.toXML();
CharSequence xml = affiliationsExtension.toXML(PubSub.NAMESPACE);
assertXmlSimilar("<affiliations node='testNode'><affiliation xmlns='http://jabber.org/protocol/pubsub#owner' jid='one@exampleone.org' affiliation='member'/></affiliations>",
xml.toString());
xml);
}
}

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.pubsub;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.softwareinfo;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.usertune;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import java.io.IOException;
import java.net.URI;

View file

@ -16,7 +16,7 @@
*/
package org.jivesoftware.smackx.xdata;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.JidTestUtil;

View file

@ -1047,7 +1047,7 @@ public final class Roster extends Manager {
}
if (presence == null) {
if (unavailable != null) {
return unavailable.clone();
return unavailable;
}
else {
presence = synthesizeUnvailablePresence(jid);
@ -1055,7 +1055,7 @@ public final class Roster extends Manager {
}
}
else {
return presence.clone();
return presence;
}
}
}
@ -1084,7 +1084,7 @@ public final class Roster extends Manager {
return presence;
}
else {
return presence.clone();
return presence;
}
}
}
@ -1107,7 +1107,7 @@ public final class Roster extends Manager {
} else {
res = new ArrayList<>(userPresences.values().size());
for (Presence presence : userPresences.values()) {
res.add(presence.clone());
res.add(presence);
}
}
return res;
@ -1156,7 +1156,7 @@ public final class Roster extends Manager {
Presence unavailable = null;
for (Presence presence : userPresences.values()) {
if (presence.isAvailable()) {
answer.add(presence.clone());
answer.add(presence);
}
else {
unavailable = presence;
@ -1166,7 +1166,7 @@ public final class Roster extends Manager {
res = answer;
}
else if (unavailable != null) {
res = Arrays.asList(unavailable.clone());
res = Arrays.asList(unavailable);
}
else {
Presence presence = synthesizeUnvailablePresence(jid);

View file

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

View file

@ -480,6 +480,8 @@ public class XmppConnectionManager {
synchronized (connectionPool) {
connectionPool.put(connectionClass, connection);
}
} else {
connection.disconnect();
}
// Note that we do not delete the account of the unauthenticated connection here, as it is done at the end of
// the test run together with all other dynamically created accounts.

View file

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

View file

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

View file

@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.concurrent.TimeoutException;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.SmackException.NoResponseException;
@ -39,6 +40,7 @@ import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.util.ResultSyncPoint;
import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
@ -120,4 +122,41 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest {
mucAsSeenByOne.leave();
mucAsSeenByTwo.leave();
}
@SmackIntegrationTest
public void mucDestroyTest() throws TimeoutException, Exception {
EntityBareJid mucAddress = JidCreate.entityBareFrom(Localpart.from("smack-inttest-join-leave-" + randomString),
mucService.getDomain());
MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress);
muc.join(Resourcepart.from("nick-one"));
final SimpleResultSyncPoint mucDestroyed = new SimpleResultSyncPoint();
@SuppressWarnings("deprecation")
DefaultUserStatusListener userStatusListener = new DefaultUserStatusListener() {
@Override
public void roomDestroyed(MultiUserChat alternateMUC, String reason) {
mucDestroyed.signal();
}
};
muc.addUserStatusListener(userStatusListener);
assertTrue(mucManagerOne.getJoinedRooms().size() == 1);
assertTrue(muc.getOccupantsCount() == 1);
assertTrue(muc.getNickname() != null);
try {
muc.destroy("Dummy reason", null);
mucDestroyed.waitForResult(timeout);
} finally {
muc.removeUserStatusListener(userStatusListener);
}
assertTrue(mucManagerOne.getJoinedRooms().size() == 0);
assertTrue(muc.getOccupantsCount() == 0);
assertTrue(muc.getNickname() == null);
}
}

View file

@ -18,7 +18,7 @@ package org.jivesoftware.smackx.ox;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;

View file

@ -17,7 +17,7 @@
package org.jivesoftware.smackx.ox;
import static junit.framework.TestCase.assertEquals;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import java.io.IOException;
import java.text.ParseException;

View file

@ -17,7 +17,7 @@
package org.jivesoftware.smackx.ox;
import static junit.framework.TestCase.assertEquals;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import java.util.Date;

View file

@ -17,7 +17,7 @@
package org.jivesoftware.smackx.ox;
import static junit.framework.TestCase.assertTrue;
import static org.jivesoftware.smack.test.util.XmlUnitUtils.assertXmlSimilar;
import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar;
import java.nio.charset.Charset;
import java.util.Arrays;

View file

@ -56,7 +56,6 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.jupiter.api.Test;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.JidTestUtil;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
public class OXInstantMessagingManagerTest extends SmackTestSuite {
@ -71,16 +70,10 @@ public class OXInstantMessagingManagerTest extends SmackTestSuite {
public void test() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
NoSuchProviderException, SmackException, MissingUserIdOnKeyException, InterruptedException, XMPPException,
XmlPullParserException {
DummyConnection aliceCon = new DummyConnection(
DummyConnection.DummyConnectionConfiguration.builder()
.setXmppDomain(JidTestUtil.EXAMPLE_ORG)
.setUsernameAndPassword("alice", "dummypass").build());
DummyConnection aliceCon = new DummyConnection();
aliceCon.connect().login();
DummyConnection bobCon = new DummyConnection(
DummyConnection.DummyConnectionConfiguration.builder()
.setXmppDomain(JidTestUtil.EXAMPLE_ORG)
.setUsernameAndPassword("bob", "dummypass").build());
DummyConnection bobCon = new DummyConnection();
bobCon.connect().login();
FileBasedOpenPgpStore aliceStore = new FileBasedOpenPgpStore(new File(basePath, "alice"));

View file

@ -26,10 +26,9 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jivesoftware.smack.SmackException.ConnectionException;
import org.jivesoftware.smack.SmackException.EndpointConnectionException;
import org.jivesoftware.smack.SynchronizationPoint;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.fsm.StateTransitionResult;
import org.jivesoftware.smack.tcp.XmppTcpTransportModule.EstablishingTcpConnectionState;
import org.jivesoftware.smack.tcp.rce.Rfc6120TcpRemoteConnectionEndpoint;
import org.jivesoftware.smack.util.Async;
@ -48,7 +47,10 @@ public final class ConnectionAttemptState {
final SocketChannel socketChannel;
final List<RemoteConnectionException<?>> connectionExceptions;
final SynchronizationPoint<ConnectionException> tcpConnectionEstablishedSyncPoint;
EndpointConnectionException connectionException;
boolean connected;
long deadline;
final Iterator<Rfc6120TcpRemoteConnectionEndpoint> connectionEndpointIterator;
/** The current connection endpoint we are trying */
@ -65,17 +67,32 @@ public final class ConnectionAttemptState {
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
connectionEndpointIterator = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.iterator();
List<Rfc6120TcpRemoteConnectionEndpoint> endpoints = discoveredEndpoints.result.discoveredRemoteConnectionEndpoints;
connectionEndpointIterator = endpoints.iterator();
connectionEndpoint = connectionEndpointIterator.next();
connectionExceptions = new ArrayList<>(discoveredEndpoints.result.discoveredRemoteConnectionEndpoints.size());
tcpConnectionEstablishedSyncPoint = new SynchronizationPoint<>(connectionInternal.connection,
"TCP connection establishment");
connectionExceptions = new ArrayList<>(endpoints.size());
}
void establishTcpConnection() {
StateTransitionResult.Failure establishTcpConnection() throws InterruptedException {
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> address = nextAddress();
establishTcpConnection(address);
synchronized (this) {
while (!connected && connectionException == null) {
final long now = System.currentTimeMillis();
if (now >= deadline) {
return new StateTransitionResult.FailureCausedByTimeout("Timeout waiting to establish connection");
}
wait (deadline - now);
}
}
if (connected) {
assert connectionException == null;
// Success case: we have been able to establish a connection to one remote endpoint.
return null;
}
return new StateTransitionResult.FailureCausedByException<Exception>(connectionException);
}
private void establishTcpConnection(
@ -84,8 +101,10 @@ public final class ConnectionAttemptState {
establishingTcpConnectionState, address);
connectionInternal.invokeConnectionStateMachineListener(connectingToHostEvent);
final boolean connected;
final InetSocketAddress inetSocketAddress = address.getInetSocketAddress();
// TODO: Should use "connect timeout" instead of reply timeout. But first connect timeout needs to be moved from
// XMPPTCPConnectionConfiguration. into XMPPConnectionConfiguration.
deadline = System.currentTimeMillis() + connectionInternal.connection.getReplyTimeout();
try {
connected = socketChannel.connect(inetSocketAddress);
} catch (IOException e) {
@ -98,7 +117,9 @@ public final class ConnectionAttemptState {
establishingTcpConnectionState, address, true);
connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
tcpConnectionEstablishedSyncPoint.reportSuccess();
synchronized (this) {
notifyAll();
}
return;
}
@ -124,9 +145,10 @@ public final class ConnectionAttemptState {
establishingTcpConnectionState, address, false);
connectionInternal.invokeConnectionStateMachineListener(connectedToHostEvent);
// Do not set 'state' here, since this is processed by a reactor thread, which doesn't hold
// the objects lock.
tcpConnectionEstablishedSyncPoint.reportSuccess();
connected = true;
synchronized (ConnectionAttemptState.this) {
notifyAll();
}
});
} catch (ClosedChannelException e) {
onIOExceptionWhenEstablishingTcpConnection(e, address);
@ -137,14 +159,14 @@ public final class ConnectionAttemptState {
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> failedAddress) {
RemoteConnectionEndpoint.InetSocketAddressCoupling<Rfc6120TcpRemoteConnectionEndpoint> nextInetSocketAddress = nextAddress();
if (nextInetSocketAddress == null) {
EndpointConnectionException connectionException = EndpointConnectionException.from(
connectionException = EndpointConnectionException.from(
discoveredEndpoints.result.lookupFailures, connectionExceptions);
tcpConnectionEstablishedSyncPoint.reportFailure(connectionException);
synchronized (this) {
notifyAll();
}
return;
}
tcpConnectionEstablishedSyncPoint.resetTimeout();
RemoteConnectionException<Rfc6120TcpRemoteConnectionEndpoint> rce = new RemoteConnectionException<>(
failedAddress, exception);
connectionExceptions.add(rce);

View file

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

View file

@ -26,7 +26,7 @@ import org.jivesoftware.smack.ConnectionConfiguration;
* </p>
* <pre>
* {@code
* XMPPTCPConnectionConfiguration conf = XMPPConnectionConfiguration.builder()
* XMPPTCPConnectionConfiguration conf = XMPPTCPConnectionConfiguration.builder()
* .setXmppDomain("example.org").setUsernameAndPassword("user", "password")
* .setCompressionEnabled(false).build();
* XMPPTCPConnection connection = new XMPPTCPConnection(conf);

View file

@ -47,17 +47,13 @@ import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.ConnectionException;
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.SecurityRequiredByClientException;
import org.jivesoftware.smack.SmackException.SecurityRequiredByServerException;
import org.jivesoftware.smack.SmackException.SmackWrappedException;
import org.jivesoftware.smack.SmackException.SmackCertificateException;
import org.jivesoftware.smack.SmackFuture;
import org.jivesoftware.smack.SmackFuture.InternalSmackFuture;
import org.jivesoftware.smack.SmackReactor.SelectionKeyAttachment;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XmppInputOutputFilter;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ConnectedButUnauthenticatedStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.LookupRemoteConnectionEndpointsStateDescriptor;
@ -591,7 +587,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
@Override
protected List<SmackFuture<LookupConnectionEndpointsResult, Exception>> lookupConnectionEndpoints() {
// Assert that there are no stale discovred endpoints prior performing the lookup.
// Assert that there are no stale discovered endpoints prior performing the lookup.
assert discoveredTcpEndpoints == null;
List<SmackFuture<LookupConnectionEndpointsResult, Exception>> futures = new ArrayList<>(2);
@ -744,23 +740,14 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
@Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws InterruptedException, ConnectionUnexpectedTerminatedException, NotConnectedException,
NoResponseException, IOException {
throws InterruptedException, IOException, SmackException, XMPPException {
// The fields inetSocketAddress and failedAddresses are handed over from LookupHostAddresses to
// ConnectingToHost.
ConnectionAttemptState connectionAttemptState = new ConnectionAttemptState(connectionInternal, discoveredTcpEndpoints,
this);
connectionAttemptState.establishTcpConnection();
try {
connectionAttemptState.tcpConnectionEstablishedSyncPoint.checkIfSuccessOrWaitOrThrow();
} catch (ConnectionException | NoResponseException e) {
// TODO: It is not really elegant that we catch the exception here. Ideally ConnectionAttemptState would
// simply return a StateTranstionResult.FailureCausedByException.
return new StateTransitionResult.FailureCausedByException<>(e);
} catch (SmackWrappedException e) {
// Should never throw SmackWrappedException.
throw new AssertionError(e);
StateTransitionResult.Failure failure = connectionAttemptState.establishTcpConnection();
if (failure != null) {
return failure;
}
socketChannel = connectionAttemptState.socketChannel;
@ -858,8 +845,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
@Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws SmackWrappedException, FailedNonzaException, IOException, InterruptedException,
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
throws IOException, InterruptedException, SmackException, XMPPException {
connectionInternal.sendAndWaitForResponse(StartTls.INSTANCE, TlsProceed.class, TlsFailure.class);
SmackTlsContext smackTlsContext = connectionInternal.getSmackTlsContext();
@ -881,7 +867,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
try {
tlsState.waitForHandshakeFinished();
} catch (CertificateException e) {
throw new SmackWrappedException(e);
throw new SmackCertificateException(e);
}
connectionInternal.newStreamOpenWaitForFeaturesSequence("stream features after TLS established");
@ -1166,43 +1152,20 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
private void handleSslException(SSLException e) {
handshakeException = e;
handshakeStatus = TlsHandshakeStatus.failed;
synchronized (this) {
notifyAll();
}
connectionInternal.notifyWaitingThreads();
}
private void onHandshakeFinished() {
handshakeStatus = TlsHandshakeStatus.successful;
synchronized (this) {
notifyAll();
}
connectionInternal.notifyWaitingThreads();
}
private boolean isHandshakeFinished() {
return handshakeStatus == TlsHandshakeStatus.successful || handshakeStatus == TlsHandshakeStatus.failed;
}
private void waitForHandshakeFinished() throws InterruptedException, CertificateException, SSLException, ConnectionUnexpectedTerminatedException, NoResponseException {
final long deadline = System.currentTimeMillis() + connectionInternal.connection.getReplyTimeout();
Exception currentConnectionException = null;
synchronized (this) {
while (!isHandshakeFinished()
&& (currentConnectionException = connectionInternal.getCurrentConnectionException()) == null) {
final long now = System.currentTimeMillis();
if (now >= deadline)
break;
wait(deadline - now);
}
}
if (currentConnectionException != null) {
throw new SmackException.ConnectionUnexpectedTerminatedException(currentConnectionException);
}
if (!isHandshakeFinished()) {
throw NoResponseException.newWith(connectionInternal.connection, "TLS handshake to finish");
}
private void waitForHandshakeFinished() throws InterruptedException, CertificateException, SSLException, SmackException, XMPPException {
connectionInternal.waitForCondition(() -> isHandshakeFinished(), "TLS handshake to finish");
if (handshakeStatus == TlsHandshakeStatus.failed) {
throw handshakeException;
@ -1235,7 +1198,7 @@ public class XmppTcpTransportModule extends ModularXmppClientToServerConnectionM
@Override
public void waitUntilInputOutputClosed() throws IOException, CertificateException, InterruptedException,
ConnectionUnexpectedTerminatedException, NoResponseException {
SmackException, XMPPException {
waitForHandshakeFinished();
}

View file

@ -1,6 +1,6 @@
/**
*
* Copyright 2014-2019 Florian Schmaus
* Copyright 2014-2020 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -124,8 +124,6 @@ public class PacketWriterTest {
// Not really cool, but may increases the chances for 't' to block in sendStanza.
Thread.sleep(250);
// Set to true for testing purposes, so that shutdown() won't wait packet writer
pw.shutdownDone.reportSuccess();
// Shutdown the packetwriter, this will also interrupt the writer thread, which is what we hope to happen in the
// thread created above.
pw.shutdown(false);

View file

@ -1 +1 @@
4.4.0-alpha4-SNAPSHOT
4.4.0-alpha5-SNAPSHOT