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 6cd744bf0..ddb64eb17 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java @@ -170,16 +170,20 @@ public abstract class SmackFuture implements Future, return exception; } + 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() { @@ -188,6 +192,7 @@ public abstract class SmackFuture implements Future, }); } else if (exception != null && exceptionCallback != null) { + callbacksInvoked = true; AbstractXMPPConnection.asyncGo(new Runnable() { @Override public void run() { 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."); 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 ba8129032..069036588 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 @@ -30,7 +30,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 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 ddcb5a8ac..6b4ef4241 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 @@ -501,6 +501,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. * @@ -518,18 +535,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(); @@ -571,7 +577,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); @@ -585,10 +591,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; 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 1ede61781..21b192b4c 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 @@ -288,8 +288,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; } 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 57734b9c3..3f8df090c 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-2021 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,9 +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; @@ -58,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; } @@ -132,7 +151,7 @@ public class SmackTestUtil { } @SuppressWarnings("unchecked") - private static > P providerClassToProvider(Class

providerClass) { + private static > P providerClassToProvider(Class

providerClass) { P provider; try { 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 0f0c1d759..da3c250ee 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-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. @@ -105,9 +105,17 @@ public class JingleReason implements XmlElement { } protected final Reason reason; + private final String text; + private final XmlElement element; public JingleReason(Reason reason) { + this(reason, null, null); + } + + public JingleReason(Reason reason, String text, XmlElement element) { this.reason = reason; + this.text = text; + this.element = element; } @Override @@ -120,6 +128,26 @@ public class JingleReason implements XmlElement { 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 XmlElement getElement() { + return element; + } + @Override public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) { XmlStringBuilder xml = new XmlStringBuilder(this, enclosingXmlEnvironment); @@ -142,7 +170,11 @@ public class JingleReason implements XmlElement { private final String sessionId; public AlternativeSession(String sessionId) { - super(Reason.alternative_session); + this(sessionId, null, null); + } + + public AlternativeSession(String sessionId, String text, XmlElement 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..40dfe9fc4 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. @@ -21,10 +21,12 @@ import java.util.logging.Logger; import org.jivesoftware.smack.packet.IqData; import org.jivesoftware.smack.packet.StandardExtensionElement; +import org.jivesoftware.smack.packet.XmlElement; 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; + XmlElement 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..9385a88f8 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.StandardExtensionElement; +import org.jivesoftware.smack.packet.XmlElement; +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()); + + XmlElement 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; } }