From 1a727c152e9af15d519a7af6663798a210c13447 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 22 Jan 2022 10:24:01 +0100 Subject: [PATCH 1/7] [core] Improve warning message of ExceptionThrowingCallbackWithHint --- .../smack/parsing/ExceptionThrowingCallbackWithHint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/parsing/ExceptionThrowingCallbackWithHint.java b/smack-core/src/main/java/org/jivesoftware/smack/parsing/ExceptionThrowingCallbackWithHint.java index f7e91355d..b2b2c47ab 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/parsing/ExceptionThrowingCallbackWithHint.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/parsing/ExceptionThrowingCallbackWithHint.java @@ -34,7 +34,7 @@ public class ExceptionThrowingCallbackWithHint extends ExceptionThrowingCallback @Override public void handleUnparsableStanza(UnparseableStanza packetData) throws IOException { - LOGGER.warning("Parsing exception encountered." + LOGGER.warning("Parsing exception \"" + packetData.getParsingException().getMessage() + "\" encountered." + " This exception will be re-thrown, leading to a disconnect." + " You can change this behavior by setting a different ParsingExceptionCallback using setParsingExceptionCallback()." + " More information an be found in AbstractXMPPConnection's javadoc."); From 1dae0c0c32e73035b47fd9efcf0e8695addf910b Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 22 Jan 2022 11:08:35 +0100 Subject: [PATCH 2/7] [core] Use Enum.toString() in XmlStringBuilder.attribute(String, Enum) All other enum-using methods of XmlStringBuilder already use Enum.toString(), as opposed to Enum.name(), this was the only left. I do not remember why I did not to change this method too, probably because of its plenty call sites. But since this method already broke Jingle XML serializaton, JingleAction was e.g., 'session_accept' when it should be 'session-accept', we change it now. Fixes SMACK-921. --- .../java/org/jivesoftware/smack/util/XmlStringBuilder.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java index 5a1fae11f..20891c838 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java @@ -289,8 +289,7 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element { public XmlStringBuilder attribute(String name, Enum value) { assert value != null; - // TODO: Should use toString() instead of name(). - attribute(name, value.name()); + attribute(name, value.toString()); return this; } From 0e637068e6de97f7646c8fa6d579c74a218a8072 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 29 Jan 2022 21:46:44 +0100 Subject: [PATCH 3/7] [core] Factor PacketParserUtils.parseIqData() in extra method --- .../smack/util/PacketParserUtils.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java index d5168ba50..82cace29d 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java @@ -500,6 +500,23 @@ public class PacketParserUtils { return parseIQ(parser, null); } + public static IqData parseIqData(XmlPullParser parser) throws XmppStringprepException { + final String id = parser.getAttributeValue("", "id"); + IqData iqData = StanzaBuilder.buildIqData(id); + + final Jid to = ParserUtils.getJidAttribute(parser, "to"); + iqData.to(to); + + final Jid from = ParserUtils.getJidAttribute(parser, "from"); + iqData.from(from); + + String typeString = parser.getAttributeValue("", "type"); + final IQ.Type type = IQ.Type.fromString(typeString); + iqData.ofType(type); + + return iqData; + } + /** * Parses an IQ packet. * @@ -517,18 +534,7 @@ public class PacketParserUtils { XmlEnvironment iqXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment); IQ iqPacket = null; StanzaError error = null; - - final String id = parser.getAttributeValue("", "id"); - IqData iqData = StanzaBuilder.buildIqData(id); - - final Jid to = ParserUtils.getJidAttribute(parser, "to"); - iqData.to(to); - - final Jid from = ParserUtils.getJidAttribute(parser, "from"); - iqData.from(from); - - final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type")); - iqData.ofType(type); + IqData iqData = parseIqData(parser); outerloop: while (true) { XmlPullParser.Event eventType = parser.next(); @@ -570,7 +576,7 @@ public class PacketParserUtils { } // Decide what to do when an IQ packet was not understood if (iqPacket == null) { - switch (type) { + switch (iqData.getType()) { case error: // If an IQ packet wasn't created above, create an empty error IQ packet. iqPacket = new ErrorIQ(error); @@ -584,10 +590,10 @@ public class PacketParserUtils { } // Set basic values on the iq packet. - iqPacket.setStanzaId(id); - iqPacket.setTo(to); - iqPacket.setFrom(from); - iqPacket.setType(type); + iqPacket.setStanzaId(iqData.getStanzaId()); + iqPacket.setTo(iqData.getTo()); + iqPacket.setFrom(iqData.getFrom()); + iqPacket.setType(iqData.getType()); iqPacket.setError(error); return iqPacket; From c06cf72337c0ef439f2ab4a21ab461e28b083b46 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 29 Jan 2022 21:47:04 +0100 Subject: [PATCH 4/7] [core] Support IqProvider in SmackTestUtil --- .../smack/test/util/SmackTestUtil.java | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestUtil.java b/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestUtil.java index a81c11c97..d7555d048 100644 --- a/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestUtil.java +++ b/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestUtil.java @@ -1,6 +1,6 @@ /** * - * Copyright 2019 Florian Schmaus + * Copyright 2019-2022 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,8 +28,13 @@ import java.util.function.Predicate; import javax.xml.namespace.QName; import org.jivesoftware.smack.packet.Element; +import org.jivesoftware.smack.packet.IqData; import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.provider.AbstractProvider; +import org.jivesoftware.smack.provider.IqProvider; import org.jivesoftware.smack.provider.Provider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; import org.jivesoftware.smack.xml.XmlPullParserFactory; @@ -57,41 +62,56 @@ public class SmackTestUtil { } } - public static > E parse(CharSequence xml, Class

providerClass, XmlPullParserKind parserKind) + public static > E parse(CharSequence xml, Class

providerClass, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { P provider = providerClassToProvider(providerClass); return parse(xml, provider, parserKind); } - public static > E parse(InputStream inputStream, Class

providerClass, XmlPullParserKind parserKind) + public static > E parse(InputStream inputStream, Class

providerClass, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { P provider = providerClassToProvider(providerClass); return parse(inputStream, provider, parserKind); } - public static > E parse(Reader reader, Class

providerClass, XmlPullParserKind parserKind) + public static > E parse(Reader reader, Class

providerClass, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { P provider = providerClassToProvider(providerClass); return parse(reader, provider, parserKind); } - public static E parse(CharSequence xml, Provider provider, XmlPullParserKind parserKind) + public static E parse(CharSequence xml, AbstractProvider provider, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { String xmlString = xml.toString(); Reader reader = new StringReader(xmlString); return parse(reader, provider, parserKind); } - public static E parse(InputStream inputStream, Provider provider, XmlPullParserKind parserKind) + public static E parse(InputStream inputStream, AbstractProvider provider, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); return parse(inputStreamReader, provider, parserKind); } - public static E parse(Reader reader, Provider provider, XmlPullParserKind parserKind) + @SuppressWarnings("unchecked") + public static E parse(Reader reader, AbstractProvider abstractProvider, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { XmlPullParser parser = getParserFor(reader, parserKind); - E element = provider.parse(parser); + + final E element; + if (abstractProvider instanceof Provider) { + Provider provider = (Provider) abstractProvider; + element = provider.parse(parser); + } else if (abstractProvider instanceof IqProvider) { + IqData iqData = PacketParserUtils.parseIqData(parser); + parser.next(); + ParserUtils.forwardToStartElement(parser); + IqProvider iqProvider = (IqProvider) abstractProvider; + element = (E) iqProvider.parse(parser, iqData); + } else { + throw new AssertionError(); + } + return element; } @@ -126,7 +146,7 @@ public class SmackTestUtil { } @SuppressWarnings("unchecked") - private static > P providerClassToProvider(Class

providerClass) { + private static > P providerClassToProvider(Class

providerClass) { P provider; try { From 7fad14ac0cf6b1a9b156d099a6521ab5c4723bcf Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sat, 29 Jan 2022 23:09:06 +0100 Subject: [PATCH 5/7] [jingle] Improve Jingle support Fixes SMACK-922. --- .../smackx/jingle/element/JingleReason.java | 37 ++++++++- .../jingle/provider/JingleProvider.java | 68 +++++++++++++--- .../jingle/provider/JingleProviderTest.java | 77 ++++++++++++++----- 3 files changed, 149 insertions(+), 33 deletions(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java index 91c909aba..116c8375d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java @@ -1,6 +1,6 @@ /** * - * Copyright 2017-2019 Florian Schmaus + * Copyright 2017-2022 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package org.jivesoftware.smackx.jingle.element; import java.util.HashMap; import java.util.Map; +import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.FullyQualifiedElement; import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.util.StringUtils; @@ -105,9 +106,17 @@ public class JingleReason implements FullyQualifiedElement { } protected final Reason reason; + private final String text; + private final ExtensionElement element; public JingleReason(Reason reason) { + this(reason, null, null); + } + + public JingleReason(Reason reason, String text, ExtensionElement element) { this.reason = reason; + this.text = text; + this.element = element; } @Override @@ -120,6 +129,26 @@ public class JingleReason implements FullyQualifiedElement { return NAMESPACE; } + /** + * An optional text that provides human-readable information about the reason for the action. + * + * @return a human-readable text with information regarding this reason or null. + * @since 4.4.5 + */ + public String getText() { + return text; + } + + /** + * An optional element that provides more detailed machine-readable information about the reason for the action. + * + * @return an elemnet with machine-readable information about this reason or null. + * @since 4.4.5 + */ + public ExtensionElement getElement() { + return element; + } + @Override public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) { XmlStringBuilder xml = new XmlStringBuilder(this, enclosingXmlEnvironment); @@ -142,7 +171,11 @@ public class JingleReason implements FullyQualifiedElement { private final String sessionId; public AlternativeSession(String sessionId) { - super(Reason.alternative_session); + this(sessionId, null, null); + } + + public AlternativeSession(String sessionId, String text, ExtensionElement element) { + super(Reason.alternative_session, text, element); if (StringUtils.isNullOrEmpty(sessionId)) { throw new NullPointerException("SessionID must not be null or empty."); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java index f6c352a75..861375995 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java @@ -1,6 +1,6 @@ /** * - * Copyright 2017-2021 Florian Schmaus + * Copyright 2017-2022 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,14 @@ package org.jivesoftware.smackx.jingle.provider; import java.io.IOException; import java.util.logging.Logger; +import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.IqData; import org.jivesoftware.smack.packet.StandardExtensionElement; import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.parsing.SmackParsingException; import org.jivesoftware.smack.parsing.StandardExtensionElementProvider; import org.jivesoftware.smack.provider.IqProvider; +import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; @@ -76,16 +78,7 @@ public class JingleProvider extends IqProvider { builder.addJingleContent(content); break; case JingleReason.ELEMENT: - parser.next(); - String reasonString = parser.getName(); - JingleReason reason; - if (reasonString.equals("alternative-session")) { - parser.next(); - String sid = parser.nextText(); - reason = new JingleReason.AlternativeSession(sid); - } else { - reason = new JingleReason(Reason.fromString(reasonString)); - } + JingleReason reason = parseJingleReason(parser); builder.setReason(reason); break; default: @@ -178,4 +171,57 @@ public class JingleProvider extends IqProvider { return builder.build(); } + + public static JingleReason parseJingleReason(XmlPullParser parser) + throws XmlPullParserException, IOException, SmackParsingException { + ParserUtils.assertAtStartTag(parser); + final int initialDepth = parser.getDepth(); + final String jingleNamespace = parser.getNamespace(); + + JingleReason.Reason reason = null; + ExtensionElement element = null; + String text = null; + + // 'sid' is only set if the reason is 'alternative-session'. + String sid = null; + + outerloop: while (true) { + XmlPullParser.TagEvent event = parser.nextTag(); + switch (event) { + case START_ELEMENT: + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + if (namespace.equals(jingleNamespace)) { + switch (elementName) { + case "text": + text = parser.nextText(); + break; + case "alternative-session": + parser.next(); + sid = parser.nextText(); + break; + default: + reason = Reason.fromString(elementName); + break; + } + } else { + element = PacketParserUtils.parseExtensionElement(elementName, namespace, parser, null); + } + break; + case END_ELEMENT: + if (parser.getDepth() == initialDepth) { + break outerloop; + } + break; + } + } + + JingleReason res; + if (sid != null) { + res = new JingleReason.AlternativeSession(sid, text, element); + } else { + res = new JingleReason(reason, text, element); + } + return res; + } } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java index c17543148..1ca0fa71a 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2017 Florian Schmaus + * Copyright 2017-2022 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,23 +17,30 @@ package org.jivesoftware.smackx.jingle.provider; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; -import org.jivesoftware.smack.util.PacketParserUtils; -import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.StandardExtensionElement; +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.test.util.SmackTestUtil; import org.jivesoftware.smack.xml.XmlPullParserException; import org.jivesoftware.smackx.jingle.element.Jingle; import org.jivesoftware.smackx.jingle.element.JingleContentDescription; import org.jivesoftware.smackx.jingle.element.JingleContentTransport; +import org.jivesoftware.smackx.jingle.element.JingleReason; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; public class JingleProviderTest { - @Test - public void testParseUnknownJingleContentDescrption() throws Exception { + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void testParseUnknownJingleContentDescrption(SmackTestUtil.XmlPullParserKind parserKind) + throws XmlPullParserException, IOException, SmackParsingException { final String unknownJingleContentDescriptionNamespace = "urn:xmpp:jingle:unknown-description:5"; final String unknownJingleContentDescription = // @formatter:off @@ -50,8 +57,8 @@ public class JingleProviderTest { "" + ""; // @formatter:on - XmlPullParser parser = createTestJingle(unknownJingleContentDescription); - Jingle jingle = (Jingle) PacketParserUtils.parseIQ(parser); + CharSequence xml = createTestJingle(unknownJingleContentDescription); + Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind); JingleContentDescription jingleContentDescription = jingle.getSoleContentOrThrow().getDescription(); @@ -59,8 +66,10 @@ public class JingleProviderTest { assertEquals(unknownJingleContentDescriptionNamespace, parsedUnknownJingleContentDescriptionNamespace); } - @Test - public void testParseUnknownJingleContentTransport() throws Exception { + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void testParseUnknownJingleContentTransport(SmackTestUtil.XmlPullParserKind parserKind) + throws XmlPullParserException, IOException, SmackParsingException { final String unknownJingleContentTransportNamespace = "urn:xmpp:jingle:unknown-transport:foo:1"; final String unknownJingleContentTransport = // @formatter:off @@ -81,8 +90,8 @@ public class JingleProviderTest { " type='direct'/>" + ""; // @formatter:on - XmlPullParser parser = createTestJingle(unknownJingleContentTransport); - Jingle jingle = (Jingle) PacketParserUtils.parseIQ(parser); + CharSequence xml = createTestJingle(unknownJingleContentTransport); + Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind); JingleContentTransport jingleContentTransport = jingle.getSoleContentOrThrow().getTransport(); @@ -90,7 +99,38 @@ public class JingleProviderTest { assertEquals(unknownJingleContentTransportNamespace, parsedUnknownJingleContentTransportNamespace); } - private static XmlPullParser createTestJingle(String... childs) throws XmlPullParserException, IOException { + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void testReasonElementWithExtraElement(SmackTestUtil.XmlPullParserKind parserKind) + throws XmlPullParserException, IOException, SmackParsingException { + String xml = "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind); + JingleReason jingleReason = jingle.getReason(); + + assertEquals(JingleReason.Reason.success, jingleReason.asEnum()); + + ExtensionElement element = jingleReason.getElement(); + // TODO: Use JUnit 5.8's assertInstanceOf when possible + // assertInstanceOf(StandardExtesionElement.class, extraElement); + assertTrue(element instanceof StandardExtensionElement); + StandardExtensionElement extraElement = (StandardExtensionElement) element; + assertEquals("https://example.org", extraElement.getNamespace()); + assertEquals("bar", extraElement.getAttributes().get("foo")); + } + + private static CharSequence createTestJingle(String... childs) throws XmlPullParserException, IOException { StringBuilder sb = new StringBuilder(); sb.append(// @formatter:off "" + "" + + "action='session-initiate' " + + "initiator='romeo@montague.example/dr4hcr0st3lup4c' " + + "sid='851ba2'>" + "" // @formatter:on ); @@ -114,9 +154,6 @@ public class JingleProviderTest { // @formatter:on ); - String jingleStanza = sb.toString(); - - XmlPullParser parser = PacketParserUtils.getParserFor(jingleStanza); - return parser; + return sb; } } From 4cdb4acf260e9f9a2a16309d0942785d37a3121b Mon Sep 17 00:00:00 2001 From: cmeng-git Date: Mon, 24 Jan 2022 08:12:12 +0800 Subject: [PATCH 6/7] [core] Fix IQProvider javadoc It is IqProvider that should be used instead. --- .../main/java/org/jivesoftware/smack/provider/IQProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/provider/IQProvider.java b/smack-core/src/main/java/org/jivesoftware/smack/provider/IQProvider.java index d0e1f8fc3..acb256785 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/provider/IQProvider.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/provider/IQProvider.java @@ -29,7 +29,7 @@ import org.jivesoftware.smack.xml.XmlPullParserException; /** *

- * Deprecation Notice: This class is deprecated, use {@link IQProvider} instead. + * Deprecation Notice: This class is deprecated, use {@link IqProvider} instead. *

* An abstract class for parsing custom IQ packets. Each IQProvider must be registered with * the ProviderManager class for it to be used. Every implementation of this From 3ff553549a4c10ce7154db6b516e362631045586 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 2 Feb 2022 12:44:04 +0100 Subject: [PATCH 7/7] [SmackFuture] Invoke the callbacks at most once Previously, if a SmackFuture both was successful and unsuccessful, it was possible that the onSuccess() callback was invoked twice. Reported-by: Boris Grozev --- .../src/main/java/org/jivesoftware/smack/SmackFuture.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java index 08646728a..ec99e6c4a 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java @@ -162,16 +162,20 @@ public abstract class SmackFuture implements Future, return result; } + private boolean callbacksInvoked; + protected final synchronized void maybeInvokeCallbacks() { - if (cancelled) { + if (cancelled || callbacksInvoked) { return; } if ((result != null || exception != null) && completionCallback != null) { + callbacksInvoked = true; completionCallback.accept(this); } if (result != null && successCallback != null) { + callbacksInvoked = true; AbstractXMPPConnection.asyncGo(new Runnable() { @Override public void run() { @@ -180,6 +184,7 @@ public abstract class SmackFuture implements Future, }); } else if (exception != null && exceptionCallback != null) { + callbacksInvoked = true; AbstractXMPPConnection.asyncGo(new Runnable() { @Override public void run() {