diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java index 2d5aeb30e..1534a5c50 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackInitialization.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2014-2016 Florian Schmaus + * Copyright 2003-2007 Jive Software, 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. @@ -36,9 +36,12 @@ import org.jivesoftware.smack.compression.zlib.ZlibXmppCompressionFactory; import org.jivesoftware.smack.initializer.SmackInitializer; import org.jivesoftware.smack.isr.InstantStreamResumptionModuleDescriptor; import org.jivesoftware.smack.packet.Bind; +import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Message.Body; import org.jivesoftware.smack.provider.BindIQProvider; import org.jivesoftware.smack.provider.BodyElementProvider; +import org.jivesoftware.smack.provider.MessageSubjectElementProvider; +import org.jivesoftware.smack.provider.MessageThreadElementProvider; import org.jivesoftware.smack.provider.ProviderManager; import org.jivesoftware.smack.provider.SaslChallengeProvider; import org.jivesoftware.smack.provider.SaslFailureProvider; @@ -130,6 +133,8 @@ public final class SmackInitialization { ProviderManager.addIQProvider(Bind.ELEMENT, Bind.NAMESPACE, new BindIQProvider()); ProviderManager.addExtensionProvider(Body.ELEMENT, Body.NAMESPACE, new BodyElementProvider()); + ProviderManager.addExtensionProvider(Message.Thread.ELEMENT, Message.Thread.NAMESPACE, new MessageThreadElementProvider()); + ProviderManager.addExtensionProvider(Message.Subject.ELEMENT, Message.Subject.NAMESPACE, new MessageSubjectElementProvider()); ProviderManager.addNonzaProvider(SaslChallengeProvider.INSTANCE); ProviderManager.addNonzaProvider(SaslSuccessProvider.INSTANCE); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java index 58d69da59..405273fbd 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java @@ -26,9 +26,11 @@ import java.util.Set; import javax.xml.namespace.QName; +import org.jivesoftware.smack.packet.Message.Thread; 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; @@ -67,7 +69,6 @@ public final class Message extends MessageOrPresence public static final String BODY = "body"; private Type type; - private String thread = null; /** * Creates a new, "normal" message. @@ -150,7 +151,6 @@ public final class Message extends MessageOrPresence Message(MessageBuilder messageBuilder) { super(messageBuilder); type = messageBuilder.type; - thread = messageBuilder.thread; } /** @@ -165,7 +165,6 @@ public final class Message extends MessageOrPresence public Message(Message other) { super(other); this.type = other.type; - this.thread = other.thread; } @Override @@ -502,7 +501,11 @@ public final class Message extends MessageOrPresence * @return the thread id of the message, or null if it doesn't exist. */ public String getThread() { - return thread; + Message.Thread thread = getExtension(Message.Thread.class); + if (thread == null) { + return null; + } + return thread.getThread(); } /** @@ -515,7 +518,7 @@ public final class Message extends MessageOrPresence @Deprecated // TODO: Remove when stanza builder is ready. public void setThread(String thread) { - this.thread = thread; + addExtension(new Message.Thread(thread)); } private String determineLanguage(String language) { @@ -559,7 +562,6 @@ public final class Message extends MessageOrPresence buf.optAttribute("type", type); buf.rightAngleBracket(); - buf.optElement("thread", thread); // Append the error subpacket if the message type is an error. if (type == Type.error) { appendErrorIfExists(buf); @@ -754,6 +756,60 @@ public final class Message extends MessageOrPresence } + @SuppressWarnings("JavaLangClash") + public static class Thread implements ExtensionElement { + public static final String ELEMENT = "thread"; + public static final String NAMESPACE = StreamOpen.CLIENT_NAMESPACE; + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); + + public static final String PARENT_ATTRIBUTE_NAME = "parent"; + + private final String thread; + private final String parent; + + public Thread(String thread) { + this(thread, null); + } + + public Thread(String thread, String parent) { + this.thread = StringUtils.requireNotNullNorEmpty(thread, "thread must not be null nor empty"); + this.parent = StringUtils.requireNullOrNotEmpty(parent, "parent must be null or not empty"); + } + + public String getThread() { + return thread; + } + + public String getParent() { + return parent; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public QName getQName() { + return QNAME; + } + + @Override + public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { + XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment); + xml.optAttribute(PARENT_ATTRIBUTE_NAME, parent); + xml.rightAngleBracket(); + xml.escape(thread); + xml.closeElement(this); + return xml; + } + } + /** * Represents the type of a message. */ diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageBuilder.java index 027e2b0cc..11552eca8 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageBuilder.java @@ -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. @@ -29,8 +29,6 @@ public final class MessageBuilder extends MessageOrPresenceBuilder { + + @Override + public Message.Subject parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) + throws XmlPullParserException, IOException, SmackParsingException { + String xmlLangSubject = ParserUtils.getXmlLang(parser); + String subject = parser.nextText(); + + return new Message.Subject(xmlLangSubject, subject); + } + +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/provider/MessageThreadElementProvider.java b/smack-core/src/main/java/org/jivesoftware/smack/provider/MessageThreadElementProvider.java new file mode 100644 index 000000000..05b2ec8d6 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/provider/MessageThreadElementProvider.java @@ -0,0 +1,38 @@ +/** + * + * 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. + * 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.provider; + +import java.io.IOException; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.XmlEnvironment; +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.xml.XmlPullParserException; + +public class MessageThreadElementProvider extends ExtensionElementProvider { + + @Override + public Message.Thread parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) + throws XmlPullParserException, IOException, SmackParsingException { + String parent = parser.getAttributeValue(Message.Thread.PARENT_ATTRIBUTE_NAME); + String thread = parser.nextText(); + + return new Message.Thread(thread, parent); + } + +} 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 4943b91bf..70861812b 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 @@ -179,7 +179,6 @@ public class PacketParserUtils { // Parse sub-elements. We include extra logic to make sure the values // are only read once. This is because it's possible for the names to appear // in arbitrary sub-elements. - String thread = null; outerloop: while (true) { XmlPullParser.Event eventType = parser.next(); switch (eventType) { @@ -187,18 +186,6 @@ public class PacketParserUtils { String elementName = parser.getName(); String namespace = parser.getNamespace(); switch (elementName) { - case "subject": - String xmlLangSubject = ParserUtils.getXmlLang(parser); - String subject = parseElementText(parser); - - Message.Subject subjectExtensionElement = new Message.Subject(xmlLangSubject, subject); - message.addExtension(subjectExtensionElement); - break; - case "thread": - if (thread == null) { - thread = parser.nextText(); - } - break; case "error": message.setError(parseError(parser, messageXmlEnvironment)); break; @@ -217,8 +204,6 @@ public class PacketParserUtils { } } - message.setThread(thread); - // TODO check for duplicate body elements. This means we need to check for duplicate xml:lang pairs and for // situations where we have a body element with an explicit xml lang set and once where the value is inherited // and both values are equal. diff --git a/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java b/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java index d6e243351..f775a4fcc 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/util/PacketParserUtilsTest.java @@ -223,6 +223,7 @@ public class PacketParserUtilsTest { // message has default language, subject has no language control = XMLBuilder.create("message") + .ns(StreamOpen.CLIENT_NAMESPACE) .a("from", "romeo@montague.lit/orchard") .a("to", "juliet@capulet.lit/balcony") .a("id", "zid615d9") @@ -239,10 +240,11 @@ public class PacketParserUtilsTest { assertTrue(message.getSubjectLanguages().isEmpty()); assertEquals(defaultLanguage, message.getSubject(defaultLanguage)); assertNull(message.getSubject(otherLanguage)); - assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); + assertXmlSimilar(control, message.toXML()); // message has non-default language, subject has no language control = XMLBuilder.create("message") + .ns(StreamOpen.CLIENT_NAMESPACE) .a("from", "romeo@montague.lit/orchard") .a("to", "juliet@capulet.lit/balcony") .a("id", "zid615d9") @@ -258,10 +260,11 @@ public class PacketParserUtilsTest { assertTrue(message.getSubjectLanguages().isEmpty()); assertEquals(otherLanguage, message.getSubject(otherLanguage)); assertNull(message.getSubject(defaultLanguage)); - assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); + assertXmlSimilar(control, message.toXML()); // message has no language, subject has no language control = XMLBuilder.create("message") + .ns(StreamOpen.CLIENT_NAMESPACE) .a("from", "romeo@montague.lit/orchard") .a("to", "juliet@capulet.lit/balcony") .a("id", "zid615d9") @@ -276,10 +279,11 @@ public class PacketParserUtilsTest { assertTrue(message.getSubjectLanguages().isEmpty()); assertEquals(defaultLanguage, message.getSubject(null)); assertNull(message.getSubject(otherLanguage)); - assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); + assertXmlSimilar(control, message.toXML()); // message has no language, subject has default language control = XMLBuilder.create("message") + .ns(StreamOpen.CLIENT_NAMESPACE) .a("from", "romeo@montague.lit/orchard") .a("to", "juliet@capulet.lit/balcony") .a("id", "zid615d9") @@ -295,10 +299,11 @@ public class PacketParserUtilsTest { assertEquals(defaultLanguage, message.getSubject(defaultLanguage)); assertNull(message.getSubject(otherLanguage)); - assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); + assertXmlSimilar(control, message.toXML()); // message has no language, subject has non-default language control = XMLBuilder.create("message") + .ns(StreamOpen.CLIENT_NAMESPACE) .a("from", "romeo@montague.lit/orchard") .a("to", "juliet@capulet.lit/balcony") .a("id", "zid615d9") @@ -315,10 +320,11 @@ public class PacketParserUtilsTest { assertTrue(message.getSubjectLanguages().contains(otherLanguage)); assertEquals(otherLanguage, message.getSubject(otherLanguage)); assertNull(message.getSubject(defaultLanguage)); - assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); + assertXmlSimilar(control, message.toXML()); // message has default language, subject has non-default language control = XMLBuilder.create("message") + .ns(StreamOpen.CLIENT_NAMESPACE) .a("from", "romeo@montague.lit/orchard") .a("to", "juliet@capulet.lit/balcony") .a("id", "zid615d9") @@ -336,10 +342,11 @@ public class PacketParserUtilsTest { assertTrue(message.getSubjectLanguages().contains(otherLanguage)); assertEquals(otherLanguage, message.getSubject(otherLanguage)); assertNull(message.getSubject(defaultLanguage)); - assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); + assertXmlSimilar(control, message.toXML()); // message has non-default language, subject has default language control = XMLBuilder.create("message") + .ns(StreamOpen.CLIENT_NAMESPACE) .a("from", "romeo@montague.lit/orchard") .a("to", "juliet@capulet.lit/balcony") .a("id", "zid615d9") @@ -357,7 +364,7 @@ public class PacketParserUtilsTest { assertTrue(message.getSubjectLanguages().contains(defaultLanguage)); assertEquals(defaultLanguage, message.getSubject(defaultLanguage)); assertNull(message.getSubject(otherLanguage)); - assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); + assertXmlSimilar(control, message.toXML()); } @@ -523,6 +530,7 @@ public class PacketParserUtilsTest { // message has default language, first subject no language, second subject other language control = XMLBuilder.create("message") + .ns(StreamOpen.CLIENT_NAMESPACE) .a("from", "romeo@montague.lit/orchard") .a("to", "juliet@capulet.lit/balcony") .a("id", "zid615d9") @@ -544,10 +552,11 @@ public class PacketParserUtilsTest { assertEquals(2, message.getSubjects().size()); assertEquals(1, message.getSubjectLanguages().size()); assertTrue(message.getSubjectLanguages().contains(otherLanguage)); - assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); + assertXmlSimilar(control, message.toXML()); // message has non-default language, first subject no language, second subject default language control = XMLBuilder.create("message") + .ns(StreamOpen.CLIENT_NAMESPACE) .a("from", "romeo@montague.lit/orchard") .a("to", "juliet@capulet.lit/balcony") .a("id", "zid615d9") @@ -569,7 +578,7 @@ public class PacketParserUtilsTest { assertEquals(2, message.getSubjects().size()); assertEquals(1, message.getSubjectLanguages().size()); assertTrue(message.getSubjectLanguages().contains(defaultLanguage)); - assertXmlSimilar(control, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); + assertXmlSimilar(control, message.toXML()); /* // message has no language, first subject no language, second subject default language diff --git a/smack-im/src/test/java/org/jivesoftware/smack/chat/ChatConnectionTest.java b/smack-im/src/test/java/org/jivesoftware/smack/chat/ChatConnectionTest.java index a30c2e066..4fcde6fba 100644 --- a/smack-im/src/test/java/org/jivesoftware/smack/chat/ChatConnectionTest.java +++ b/smack-im/src/test/java/org/jivesoftware/smack/chat/ChatConnectionTest.java @@ -336,7 +336,9 @@ public class ChatConnectionTest { jid = JidTestUtil.DUMMY_AT_EXAMPLE_ORG; } chatMsg.from(jid); - chatMsg.setThread(threadId); + if (threadId != null) { + chatMsg.setThread(threadId); + } return chatMsg; }