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

Merge branch '4.2'

This commit is contained in:
Florian Schmaus 2017-10-14 14:56:36 +02:00
commit 384c285fbc
20 changed files with 303 additions and 79 deletions

View file

@ -43,7 +43,10 @@ import org.jivesoftware.smack.util.Async;
* </ol>
*
* {@link ReconnectionPolicy#FIXED_DELAY} - The reconnection mechanism will try to reconnect after a fixed delay
* independently from the number of reconnection attempts already performed
* independently from the number of reconnection attempts already performed.
* <p>
* Interrupting the reconnection thread will abort the reconnection mechanism.
* </p>
*
* @author Francisco Vives
* @author Luca Stucchi
@ -163,7 +166,7 @@ public final class ReconnectionManager {
private ReconnectionManager(AbstractXMPPConnection connection) {
weakRefConnection = new WeakReference<AbstractXMPPConnection>(connection);
reconnectionRunnable = new Thread() {
reconnectionRunnable = new Runnable() {
/**
* Holds the current number of reconnection attempts
@ -211,6 +214,10 @@ public final class ReconnectionManager {
if (connection == null) {
return;
}
// Reset attempts to zero since a new reconnection cycle is started once this runs.
attempts = 0;
// The process will try to reconnect until the connection is established or
// the user cancel the reconnection process AbstractXMPPConnection.disconnect().
while (isReconnectionPossible(connection)) {
@ -219,7 +226,10 @@ public final class ReconnectionManager {
// Sleep until we're ready for the next reconnection attempt. Notify
// listeners once per second about how much time remains before the next
// reconnection attempt.
while (isReconnectionPossible(connection) && remainingSeconds > 0) {
while (remainingSeconds > 0) {
if (!isReconnectionPossible(connection)) {
return;
}
try {
Thread.sleep(1000);
remainingSeconds--;
@ -228,8 +238,9 @@ public final class ReconnectionManager {
}
}
catch (InterruptedException e) {
LOGGER.log(Level.FINE, "waiting for reconnection interrupted", e);
break;
LOGGER.log(Level.FINE, "Reconnection Thread was interrupted, aborting reconnection mechanism", e);
// Exit the reconnection thread in case it was interrupted.
return;
}
}
@ -237,24 +248,18 @@ public final class ReconnectionManager {
listener.reconnectingIn(0);
}
if (!isReconnectionPossible(connection)) {
return;
}
// Makes a reconnection attempt
try {
if (isReconnectionPossible(connection)) {
try {
connection.connect();
} catch (SmackException.AlreadyConnectedException e) {
LOGGER.log(Level.FINER, "Connection was already connected on reconnection attempt", e);
}
try {
connection.connect();
}
// TODO Starting with Smack 4.2, connect() will no
// longer login automatically. So change this and the
// previous lines to connection.connect().login() in the
// 4.2, or any later, branch.
if (!connection.isAuthenticated()) {
connection.login();
catch (SmackException.AlreadyConnectedException e) {
LOGGER.log(Level.FINER, "Connection was already connected on reconnection attempt", e);
}
// Successfully reconnected.
attempts = 0;
connection.login();
}
catch (SmackException.AlreadyLoggedInException e) {
// This can happen if another thread concurrently triggers a reconnection
@ -262,12 +267,21 @@ public final class ReconnectionManager {
// failure. See also SMACK-725.
LOGGER.log(Level.FINER, "Reconnection not required, was already logged in", e);
}
catch (SmackException | IOException | XMPPException | InterruptedException e) {
catch (SmackException | IOException | XMPPException e) {
// Fires the failed reconnection notification
for (ConnectionListener listener : connection.connectionListeners) {
listener.reconnectionFailed(e);
}
// Failed to reconnect, try again.
continue;
} catch (InterruptedException e) {
LOGGER.log(Level.FINE, "Reconnection Thread was interrupted, aborting reconnection mechanism", e);
// Exit the reconnection thread in case it was interrupted.
return;
}
// Successfully reconnected .
return;
}
}
};
@ -314,7 +328,7 @@ public final class ReconnectionManager {
*
* @return true, if the reconnection mechanism is enabled.
*/
public boolean isAutomaticReconnectEnabled() {
public synchronized boolean isAutomaticReconnectEnabled() {
return automaticReconnectEnabled;
}
@ -348,6 +362,20 @@ public final class ReconnectionManager {
"Smack Reconnection Manager (" + connection.getConnectionCounter() + ')');
}
/**
* Abort a possibly running reconnection mechanism.
*
* @since 4.2.2
*/
public synchronized void abortPossiblyRunningReconnection() {
if (reconnectionThread == null) {
return;
}
reconnectionThread.interrupt();
reconnectionThread = null;
}
private final ConnectionListener connectionListener = new AbstractConnectionListener() {
@Override

View file

@ -184,7 +184,7 @@ public final class SmackConfiguration {
}
}
/**
/**
* Add a Collection of SASL mechanisms to the list to be used.
*
* @param mechs the Collection of SASL mechanisms to be added
@ -204,7 +204,7 @@ public final class SmackConfiguration {
defaultMechs.remove(mech);
}
/**
/**
* Remove a Collection of SASL mechanisms to the list to be used.
*
* @param mechs the Collection of SASL mechanisms to be removed
@ -339,7 +339,7 @@ public final class SmackConfiguration {
return defaultHostnameVerififer;
}
enum UnknownIqRequestReplyMode {
public enum UnknownIqRequestReplyMode {
doNotReply,
replyFeatureNotImplemented,
replyServiceUnavailable,

View file

@ -23,6 +23,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.PacketUtil;
import org.jivesoftware.smack.util.XmlStringBuilder;
@ -85,6 +86,7 @@ public class AbstractError {
* @return the descriptive text or null.
*/
public String getDescriptiveText(String xmllang) {
Objects.requireNonNull(xmllang, "xmllang must not be null");
return descriptiveTexts.get(xmllang);
}
@ -105,7 +107,8 @@ public class AbstractError {
String xmllang = entry.getKey();
String text = entry.getValue();
xml.halfOpenElement("text").xmlnsAttribute(textNamespace)
.xmllangAttribute(xmllang).rightAngleBracket();
.optXmlLangAttribute(xmllang)
.rightAngleBracket();
xml.escape(text);
xml.closeElement("text");
}
@ -120,6 +123,15 @@ public class AbstractError {
protected List<ExtensionElement> extensions;
public B setDescriptiveTexts(Map<String, String> descriptiveTexts) {
if (descriptiveTexts == null) {
this.descriptiveTexts = null;
return getThis();
}
for (String key : descriptiveTexts.keySet()) {
if (key == null) {
throw new IllegalArgumentException("descriptiveTexts cannot contain null key");
}
}
if (this.descriptiveTexts == null) {
this.descriptiveTexts = descriptiveTexts;
}

View file

@ -38,14 +38,14 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
* <tr><td>conflict</td><td>CANCEL</td><td>8.3.3.2</td></tr>
* <tr><td>feature-not-implemented</td><td>CANCEL</td><td>8.3.3.3</td></tr>
* <tr><td>forbidden</td><td>AUTH</td><td>8.3.3.4</td></tr>
* <tr><td>gone</td><td>MODIFY</td><td>8.3.3.5</td></tr>
* <tr><td>gone</td><td>CANCEL</td><td>8.3.3.5</td></tr>
* <tr><td>internal-server-error</td><td>WAIT</td><td>8.3.3.6</td></tr>
* <tr><td>item-not-found</td><td>CANCEL</td><td>8.3.3.7</td></tr>
* <tr><td>jid-malformed</td><td>MODIFY</td><td>8.3.3.8</td></tr>
* <tr><td>not-acceptable</td><td> MODIFY</td><td>8.3.3.9</td></tr>
* <tr><td>not-acceptable</td><td>MODIFY</td><td>8.3.3.9</td></tr>
* <tr><td>not-allowed</td><td>CANCEL</td><td>8.3.3.10</td></tr>
* <tr><td>not-authorized</td><td>AUTH</td><td>8.3.3.11</td></tr>
* <tr><td>policy-violation</td><td>AUTH</td><td>8.3.3.12</td></tr>
* <tr><td>policy-violation</td><td>MODIFY</td><td>8.3.3.12</td></tr>
* <tr><td>recipient-unavailable</td><td>WAIT</td><td>8.3.3.13</td></tr>
* <tr><td>redirect</td><td>MODIFY</td><td>8.3.3.14</td></tr>
* <tr><td>registration-required</td><td>AUTH</td><td>8.3.3.15</td></tr>
@ -54,7 +54,7 @@ import org.jivesoftware.smack.util.XmlStringBuilder;
* <tr><td>resource-constraint</td><td>WAIT</td><td>8.3.3.18</td></tr>
* <tr><td>service-unavailable</td><td>CANCEL</td><td>8.3.3.19</td></tr>
* <tr><td>subscription-required</td><td>AUTH</td><td>8.3.3.20</td></tr>
* <tr><td>undefined-condition</td><td>WAIT</td><td>8.3.3.21</td></tr>
* <tr><td>undefined-condition</td><td>MODIFY</td><td>8.3.3.21</td></tr>
* <tr><td>unexpected-request</td><td>WAIT</td><td>8.3.3.22</td></tr>
* </table>
*
@ -69,7 +69,7 @@ public class XMPPError extends AbstractError {
public static final String ERROR = "error";
private static final Logger LOGGER = Logger.getLogger(XMPPError.class.getName());
private static final Map<Condition, Type> CONDITION_TO_TYPE = new HashMap<Condition, Type>();
static final Map<Condition, Type> CONDITION_TO_TYPE = new HashMap<Condition, Type>();
static {
CONDITION_TO_TYPE.put(Condition.bad_request, Type.MODIFY);
@ -90,9 +90,10 @@ public class XMPPError extends AbstractError {
CONDITION_TO_TYPE.put(Condition.remote_server_not_found, Type.CANCEL);
CONDITION_TO_TYPE.put(Condition.remote_server_timeout, Type.WAIT);
CONDITION_TO_TYPE.put(Condition.resource_constraint, Type.WAIT);
CONDITION_TO_TYPE.put(Condition.service_unavailable, Type.WAIT);
CONDITION_TO_TYPE.put(Condition.subscription_required, Type.WAIT);
CONDITION_TO_TYPE.put(Condition.unexpected_request, Type.MODIFY);
CONDITION_TO_TYPE.put(Condition.service_unavailable, Type.CANCEL);
CONDITION_TO_TYPE.put(Condition.subscription_required, Type.AUTH);
CONDITION_TO_TYPE.put(Condition.undefined_condition, Type.MODIFY);
CONDITION_TO_TYPE.put(Condition.unexpected_request, Type.WAIT);
}
private final Condition condition;

View file

@ -749,6 +749,12 @@ public class PacketParserUtils {
descriptiveTexts = new HashMap<String, String>();
}
String xmllang = getLanguageAttribute(parser);
if (xmllang == null) {
// XMPPError assumes the default locale, 'en', or the empty string.
// Establish the invariant that there is never null as a key.
xmllang = "";
}
String text = parser.nextText();
String previousValue = descriptiveTexts.put(xmllang, text);
assert (previousValue == null);

View file

@ -361,7 +361,7 @@ public class XmlStringBuilder implements Appendable, CharSequence {
}
public XmlStringBuilder optXmlLangAttribute(String lang) {
if (lang != null) {
if (!StringUtils.isNullOrEmpty(lang)) {
xmllangAttribute(lang);
}
return this;

View file

@ -59,6 +59,20 @@ public abstract class DNSResolver {
return new HostAddress(name, port, inetAddresses);
}
/**
* Lookup the IP addresses of a given host name. Returns <code>null</code> if there was an error, in which the error
* reason will be added in form of a <code>HostAddress</code> to <code>failedAddresses</code>. Returns a empty list
* in case the DNS name exists but has no associated A or AAAA resource records. Otherwise, if the resolution was
* successful <em>and</em> there is at least one A or AAAA resource record, then a non-empty list will be returned.
* <p>
* Concrete DNS resolver implementations are free to overwrite this, but have to stick to the interface contract.
* </p>
*
* @param name the DNS name to lookup
* @param failedAddresses a list with the failed addresses
* @param dnssecMode the selected DNSSEC mode
* @return A list, either empty or non-empty, or <code>null</code>
*/
protected List<InetAddress> lookupHostAddress0(String name, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
// Default implementation of a DNS name lookup for A/AAAA records. It is assumed that this method does never
// support DNSSEC. Subclasses are free to override this method.

View file

@ -0,0 +1,35 @@
/**
*
* Copyright © 2017 Ingo Bauersachs
*
* 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.packet;
import static org.jivesoftware.smack.packet.XMPPError.Condition;
import static org.jivesoftware.smack.packet.XMPPError.Type;
import static org.junit.Assert.assertEquals;
import java.util.Map;
import org.junit.Test;
public class XMPPErrorTest {
@Test
public void testConditionHasDefaultTypeMapping() throws NoSuchFieldException, IllegalAccessException {
Map<Condition, Type> conditionToTypeMap = XMPPError.CONDITION_TO_TYPE;
assertEquals("CONDITION_TO_TYPE map is likely out of sync with Condition enum",
Condition.values().length,
conditionToTypeMap.size());
}
}

View file

@ -25,7 +25,9 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import javax.xml.parsers.FactoryConfigurationError;
@ -35,6 +37,7 @@ import javax.xml.transform.TransformerException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.sasl.SASLError;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
@ -867,4 +870,44 @@ public class PacketParserUtilsTest {
return otherLanguage;
}
@Test(expected = IllegalArgumentException.class)
public void descriptiveTextNullLangPassedMap() throws Exception {
final String text = "Dummy descriptive text";
Map<String, String> texts = new HashMap<>();
texts.put(null, text);
XMPPError
.getBuilder(XMPPError.Condition.internal_server_error)
.setDescriptiveTexts(texts)
.build();
}
@Test
public void ensureNoEmptyLangInDescriptiveText() throws Exception {
final String text = "Dummy descriptive text";
Map<String, String> texts = new HashMap<>();
texts.put("", text);
XMPPError error = XMPPError
.getBuilder(XMPPError.Condition.internal_server_error)
.setDescriptiveTexts(texts)
.build();
final String errorXml = XMLBuilder
.create(XMPPError.ERROR).a("type", "cancel").up()
.element("internal-server-error", XMPPError.NAMESPACE).up()
.element("text", XMPPError.NAMESPACE).t(text).up()
.asString();
XmlUnitUtils.assertSimilar(errorXml, error.toXML());
}
@Test
public void ensureNoNullLangInParsedDescriptiveTexts() throws Exception {
final String text = "Dummy descriptive text";
final String errorXml = XMLBuilder
.create(XMPPError.ERROR).a("type", "cancel").up()
.element("internal-server-error", XMPPError.NAMESPACE).up()
.element("text", XMPPError.NAMESPACE).t(text).up()
.asString();
XmlPullParser parser = TestUtils.getParser(errorXml);
XMPPError error = PacketParserUtils.parseError(parser).build();
assertEquals(text, error.getDescriptiveText());
}
}