diff --git a/.travis.yml b/.travis.yml index 10f7b90f5..ad03d90f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,8 @@ android: components: - android-19 jdk: - - openjdk8 + - oraclejdk8 + - openjdk9 - openjdk11 before_cache: diff --git a/README.md b/README.md index 1e22423ce..7ae8508f4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Smack ===== -[![Build Status](https://api.travis-ci.com/igniterealtime/Smack.svg?branch=master)](https://travis-ci.com/github/igniterealtime/Smack) [![Coverage Status](https://coveralls.io/repos/igniterealtime/Smack/badge.svg)](https://coveralls.io/r/igniterealtime/Smack) [![Project Stats](https://www.openhub.net/p/smack/widgets/project_thin_badge.gif)](https://www.openhub.net/p/smack) [![Link to XMPP chat smack@conference.igniterealtime.org](https://inverse.chat/badge.svg?room=smack@conference.igniterealtime.org)](https://inverse.chat/#converse/room?jid=smack@conference.igniterealtime.org) +[![Build Status](https://travis-ci.org/igniterealtime/Smack.svg?branch=master)](https://travis-ci.org/igniterealtime/Smack) [![Coverage Status](https://coveralls.io/repos/igniterealtime/Smack/badge.svg)](https://coveralls.io/r/igniterealtime/Smack) [![Project Stats](https://www.openhub.net/p/smack/widgets/project_thin_badge.gif)](https://www.openhub.net/p/smack) [![Link to XMPP chat smack@conference.igniterealtime.org](https://inverse.chat/badge.svg?room=smack@conference.igniterealtime.org)](https://inverse.chat/#converse/room?jid=smack@conference.igniterealtime.org) About ----- diff --git a/build.gradle b/build.gradle index 76cf80d61..b0a81ba9f 100644 --- a/build.gradle +++ b/build.gradle @@ -101,6 +101,20 @@ allprojects { ':smack-omemo-signal-integration-test', ':smack-repl' ].collect{ project(it) } + // When this list is empty, then move the according javadoc + // tool Werror option into the global configure section. + nonStrictJavadocProjects = [ + ':smack-bosh', + ':smack-core', + ':smack-experimental', + ':smack-extensions', + ':smack-im', + ':smack-integration-test', + ':smack-jingle-old', + ':smack-legacy', + ':smack-omemo', + ':smack-tcp', + ].collect{ project(it) } // Lazily evaluate the Android bootClasspath and offline // Javadoc using a closure, so that targets which do not // require it are still able to succeed without an Android @@ -122,11 +136,9 @@ allprojects { // Default to true useSonatype = true } - javaCompatilibity = JavaVersion.VERSION_1_8 - javaMajor = javaCompatilibity.getMajorVersion() } group = 'org.igniterealtime.smack' - sourceCompatibility = javaCompatilibity + sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = sourceCompatibility version = shortVersion if (isSnapshot) { @@ -246,19 +258,7 @@ allprojects { options.addStringOption('Xwerror', '-quiet') } } - - if (JavaVersion.current().isJava9Compatible()) { - tasks.withType(Javadoc) { - options.addStringOption('-release', javaMajor) - } - tasks.withType(JavaCompile) { - options.compilerArgs.addAll([ - '--release', javaMajor, - ]) - } - } - -tasks.withType(Javadoc) { + tasks.withType(Javadoc) { options.charSet = "UTF-8" options.encoding = 'UTF-8' } @@ -304,10 +304,16 @@ task javadocAll(type: Javadoc) { project.sourceSets.main.compileClasspath}) classpath += files(androidBootClasspath) options { + // Add source compatiblitiy statement to work around bug in JDK 11 + // See + // - https://bugs.openjdk.java.net/browse/JDK-8217177 + // - http://hg.openjdk.java.net/jdk/jdk/rev/8ce4083fc831 + // - https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=920020 + source = sourceCompatibility linkSource = true use = true links = [ - "https://docs.oracle.com/javase/${javaMajor}/docs/api/", + "https://docs.oracle.com/javase/${sourceCompatibility.getMajorVersion()}/docs/api/", "https://jxmpp.org/releases/$jxmppVersion/javadoc/", "https://minidns.org/releases/$miniDnsVersion/javadoc/", ] as String[] @@ -565,6 +571,15 @@ subprojects*.jar { } } +configure(subprojects - nonStrictJavadocProjects) { + tasks.withType(Javadoc) { + // Abort on javadoc warnings. + // See JDK-8200363 (https://bugs.openjdk.java.net/browse/JDK-8200363) + // for information about the -Xwerror option. + options.addStringOption('Xwerror', '-quiet') + } +} + configure(subprojects - gplLicensedProjects) { checkstyle { configProperties.checkstyleLicenseHeader = "header" diff --git a/documentation/developer/integrationtest.md b/documentation/developer/integrationtest.md index fe006b58c..16119c6f1 100644 --- a/documentation/developer/integrationtest.md +++ b/documentation/developer/integrationtest.md @@ -102,7 +102,7 @@ The base class that integration tests need to subclass. ### `AbstractSmackLowLevelIntegrationTest` -Allows low level integration test, i.e. every test method will have its own exclusive XMPPTCPConnection instances. +Allows low level integration test, i.e. ever test method will have its on exclusive XMPPTCPConnection instances. ### `AbstractSmackSpecificLowLevelIntegrationTest` diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 91f291ca4..47100f37f 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -51,7 +51,6 @@ Smack Extensions and currently supported XEPs of smack-extensions | Result Set Management | [XEP-0059](https://xmpp.org/extensions/xep-0059.html) | n/a | Page through and otherwise manage the receipt of large result sets | | [PubSub](pubsub.md) | [XEP-0060](https://xmpp.org/extensions/xep-0060.html) | n/a | Generic publish and subscribe functionality. | | SOCKS5 Bytestreams | [XEP-0065](https://xmpp.org/extensions/xep-0065.html) | n/a | Out-of-band bytestream between any two XMPP entities. | -| Field Standardization for Data Forms | [XEP-0068](https://xmpp.org/extensions/xep-0068.html) | n/a | Standardized field variables used in the context of jabber:x:data forms. | | [XHTML-IM](xhtml.md) | [XEP-0071](https://xmpp.org/extensions/xep-0071.html) | n/a | Allows send and receiving formatted messages using XHTML. | | In-Band Registration | [XEP-0077](https://xmpp.org/extensions/xep-0077.html) | n/a | In-band registration with XMPP services. | | Advanced Message Processing | [XEP-0079](https://xmpp.org/extensions/xep-0079.html) | n/a | Enables entities to request, and servers to perform, advanced processing of XMPP message stanzas. | @@ -121,7 +120,6 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental | [Consistent Color Generation](consistent_colors.md) | [XEP-0392](https://xmpp.org/extensions/xep-0392.html) | 0.6.0 | Generate consistent colors for identifiers like usernames to provide a consistent user experience. | | [Message Markup](messagemarkup.md) | [XEP-0394](https://xmpp.org/extensions/xep-0394.html) | 0.1.0 | Style message bodies while keeping body and markup information separated. | | DNS Queries over XMPP (DoX) | [XEP-0418](https://xmpp.org/extensions/xep-0418.html) | 0.1.0 | Send DNS queries and responses over XMPP. | -| Message Fastening | [XEP-0422](https://xmpp.org/extensions/xep-0422.html) | 0.1.1 | Mark payloads on a message to be logistically fastened to a previous message. | Unofficial XMPP Extensions -------------------------- diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java index 87f6f091e..66ebb1916 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -527,8 +527,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { 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 - // it. + closingStreamReceived.init(); } /** @@ -550,7 +549,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { // Reset the connection state initState(); - closingStreamReceived.init(); streamId = null; try { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/ExtensionElementFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/ExtensionElementFilter.java deleted file mode 100644 index ff84f403d..000000000 --- a/smack-core/src/main/java/org/jivesoftware/smack/filter/ExtensionElementFilter.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * - * 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.filter; - -import javax.xml.namespace.QName; - -import org.jivesoftware.smack.packet.ExtensionElement; -import org.jivesoftware.smack.packet.Stanza; -import org.jivesoftware.smack.util.XmppElementUtil; - -public class ExtensionElementFilter implements StanzaFilter { - - private final Class extensionElementClass; - private final QName extensionElementQName; - - protected ExtensionElementFilter(Class extensionElementClass) { - this.extensionElementClass = extensionElementClass; - extensionElementQName = XmppElementUtil.getQNameFor(extensionElementClass); - } - - @Override - public final boolean accept(Stanza stanza) { - ExtensionElement extensionElement = stanza.getExtension(extensionElementQName); - if (extensionElement == null) { - return false; - } - - if (!extensionElementClass.isInstance(extensionElement)) { - return false; - } - - E specificExtensionElement = extensionElementClass.cast(extensionElement); - return accept(specificExtensionElement); - } - - public boolean accept(E extensionElement) { - return true; - } -} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java index 6d70dbae3..61d6ed276 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/MessageTypeFilter.java @@ -35,7 +35,6 @@ public final class MessageTypeFilter extends FlexibleStanzaTypeFilter { public static final StanzaFilter HEADLINE = new MessageTypeFilter(Type.headline); public static final StanzaFilter ERROR = new MessageTypeFilter(Type.error); public static final StanzaFilter NORMAL_OR_CHAT = new OrFilter(NORMAL, CHAT); - public static final StanzaFilter NORMAL_OR_HEADLINE = new OrFilter(NORMAL, HEADLINE); public static final StanzaFilter NORMAL_OR_CHAT_OR_HEADLINE = new OrFilter(NORMAL_OR_CHAT, HEADLINE); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketExtensionFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketExtensionFilter.java new file mode 100644 index 000000000..8b0f40cd5 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketExtensionFilter.java @@ -0,0 +1,79 @@ +/** + * + * Copyright 2003-2007 Jive Software. + * + * 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.filter; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.util.StringUtils; + +/** + * Filters for packets with a particular type of stanza extension. + * + * @author Matt Tucker + * @deprecated use {@link StanzaExtensionFilter} instead. + */ +@Deprecated +public class PacketExtensionFilter implements StanzaFilter { + + private final String elementName; + private final String namespace; + + /** + * Creates a new stanza extension filter. Packets will pass the filter if + * they have a stanza extension that matches the specified element name + * and namespace. + * + * @param elementName the XML element name of the stanza extension. + * @param namespace the XML namespace of the stanza extension. + */ + public PacketExtensionFilter(String elementName, String namespace) { + StringUtils.requireNotNullNorEmpty(namespace, "namespace must not be null nor empty"); + + this.elementName = elementName; + this.namespace = namespace; + } + + /** + * Creates a new stanza extension filter. Packets will pass the filter if they have a packet + * extension that matches the specified namespace. + * + * @param namespace the XML namespace of the stanza extension. + */ + public PacketExtensionFilter(String namespace) { + this(null, namespace); + } + + /** + * Creates a new stanza extension filter for the given stanza extension. + * + * @param packetExtension TODO javadoc me please + */ + public PacketExtensionFilter(ExtensionElement packetExtension) { + this(packetExtension.getElementName(), packetExtension.getNamespace()); + } + + @Override + public boolean accept(Stanza packet) { + return packet.hasExtension(elementName, namespace); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": element=" + elementName + " namespace=" + namespace; + } +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketFilter.java new file mode 100644 index 000000000..10f9b834f --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketFilter.java @@ -0,0 +1,51 @@ +/** + * + * Copyright 2003-2007 Jive Software. + * + * 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.filter; + +/** + * Defines a way to filter packets for particular attributes. Stanza filters are used when + * constructing stanza listeners or collectors -- the filter defines what packets match the criteria + * of the collector or listener for further stanza processing. + *

+ * Several simple filters are pre-defined. These filters can be logically combined for more complex + * stanza filtering by using the {@link org.jivesoftware.smack.filter.AndFilter AndFilter} and + * {@link org.jivesoftware.smack.filter.OrFilter OrFilter} filters. It's also possible to define + * your own filters by implementing this interface. The code example below creates a trivial filter + * for packets with a specific ID (real code should use {@link StanzaIdFilter} instead). + * + *

+ * // Use an anonymous inner class to define a stanza filter that returns
+ * // all packets that have a stanza ID of "RS145".
+ * PacketFilter myFilter = new PacketFilter() {
+ *     public boolean accept(Packet packet) {
+ *         return "RS145".equals(packet.getStanzaId());
+ *     }
+ * };
+ * // Create a new stanza collector using the filter we created.
+ * StanzaCollector myCollector = packetReader.createStanzaCollector(myFilter);
+ * 
+ * + * @see org.jivesoftware.smack.StanzaCollector + * @see org.jivesoftware.smack.StanzaListener + * @author Matt Tucker + * @deprecated use {@link StanzaFilter} + */ +@Deprecated +public interface PacketFilter extends StanzaFilter { + +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketIDFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketIDFilter.java new file mode 100644 index 000000000..643f3f3fe --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketIDFilter.java @@ -0,0 +1,66 @@ +/** + * + * Copyright 2003-2007 Jive Software. + * + * 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.filter; + +import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.util.StringUtils; + +/** + * Filters for packets with a particular stanza ID. + * + * @author Matt Tucker + * @deprecated use {@link StanzaIdFilter} instead. + */ +@Deprecated +public class PacketIDFilter implements StanzaFilter { + + private final String packetID; + + /** + * Creates a new stanza ID filter using the specified packet's ID. + * + * @param packet the stanza which the ID is taken from. + * @deprecated use {@link StanzaIdFilter#StanzaIdFilter(Stanza)} instead. + */ + @Deprecated + public PacketIDFilter(Stanza packet) { + this(packet.getStanzaId()); + } + + /** + * Creates a new stanza ID filter using the specified stanza ID. + * + * @param packetID the stanza ID to filter for. + * @deprecated use {@link StanzaIdFilter#StanzaIdFilter(Stanza)} instead. + */ + @Deprecated + public PacketIDFilter(String packetID) { + StringUtils.requireNotNullNorEmpty(packetID, "Packet ID must not be null nor empty."); + this.packetID = packetID; + } + + @Override + public boolean accept(Stanza packet) { + return packetID.equals(packet.getStanzaId()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": id=" + packetID; + } +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketTypeFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketTypeFilter.java new file mode 100644 index 000000000..37d37a49c --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/PacketTypeFilter.java @@ -0,0 +1,63 @@ +/** + * + * Copyright 2003-2007 Jive Software. + * + * 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.filter; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.Stanza; + +/** + * Filters for packets of a particular type. The type is given as a Class object, so + * example types would: + *
    + *
  • Message.class + *
  • IQ.class + *
  • Presence.class + *
+ * + * @author Matt Tucker + * @deprecated use {@link StanzaTypeFilter} instead. + */ +@Deprecated +public class PacketTypeFilter implements StanzaFilter { + + public static final PacketTypeFilter PRESENCE = new PacketTypeFilter(Presence.class); + public static final PacketTypeFilter MESSAGE = new PacketTypeFilter(Message.class); + + private final Class packetType; + + /** + * Creates a new stanza type filter that will filter for packets that are the + * same type as packetType. + * + * @param packetType the Class type. + */ + public PacketTypeFilter(Class packetType) { + this.packetType = packetType; + } + + @Override + public boolean accept(Stanza packet) { + return packetType.isInstance(packet); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": " + packetType.getName(); + } +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/ToFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/ToFilter.java new file mode 100644 index 000000000..94da6a332 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/ToFilter.java @@ -0,0 +1,50 @@ +/** + * + * Copyright © 2014 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.filter; + +import org.jivesoftware.smack.packet.Stanza; + +import org.jxmpp.jid.Jid; + +/** + * Match based on the 'to' attribute of a Stanza. + * + * @deprecated use {@link ToMatchesFilter} instead. + */ +@Deprecated +public class ToFilter implements StanzaFilter { + + private final Jid to; + + public ToFilter(Jid to) { + this.to = to; + } + + @Override + public boolean accept(Stanza packet) { + Jid packetTo = packet.getTo(); + if (packetTo == null) { + return false; + } + return packetTo.equals(to); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": to=" + to; + } +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/filter/jidtype/FromJidTypeFilter.java b/smack-core/src/main/java/org/jivesoftware/smack/filter/jidtype/FromJidTypeFilter.java index 3bcd71b22..c9326c175 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/filter/jidtype/FromJidTypeFilter.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/filter/jidtype/FromJidTypeFilter.java @@ -28,8 +28,6 @@ import org.jxmpp.jid.Jid; */ public class FromJidTypeFilter extends AbstractJidTypeFilter { - public static final FromJidTypeFilter ENTITY_BARE_JID = new FromJidTypeFilter(JidType.EntityBareJid); - public FromJidTypeFilter(JidType jidType) { super(jidType); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/Stanza.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/Stanza.java index 15c82127e..bedcad481 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/Stanza.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/Stanza.java @@ -445,7 +445,7 @@ public abstract class Stanza implements StanzaView, TopLevelStreamElement { * @param extensions a collection of stanza extensions */ // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone. - public final void addExtensions(Collection extensions) { + public final void addExtensions(Collection extensions) { if (extensions == null) return; for (ExtensionElement packetExtension : extensions) { addExtension(packetExtension); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaView.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaView.java index 7745ecc2a..9ee3b6933 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaView.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaView.java @@ -106,12 +106,5 @@ public interface StanzaView extends XmlLangElement { List getExtensions(QName qname); - /** - * Return all extension elements of the given type. Returns the empty list if there a none. - * - * @param the type of extension elements. - * @param extensionElementClass the class of the type of extension elements. - * @return a list of extension elements of that type, which may be empty. - */ List getExtensions(Class extensionElementClass); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/parsing/SmackParsingException.java b/smack-core/src/main/java/org/jivesoftware/smack/parsing/SmackParsingException.java index 08b4586fb..82502736f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/parsing/SmackParsingException.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/parsing/SmackParsingException.java @@ -1,6 +1,6 @@ /** * - * Copyright 2019-2020 Florian Schmaus + * Copyright 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. @@ -30,10 +30,6 @@ public class SmackParsingException extends Exception { super(exception); } - public SmackParsingException(String message) { - super(message); - } - public static class SmackTextParseException extends SmackParsingException { /** * diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java index b35dfb4a0..eb76ef3be 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java @@ -18,12 +18,9 @@ package org.jivesoftware.smack.util; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Set; public class CollectionUtil { @@ -62,20 +59,6 @@ public class CollectionUtil { return new ArrayList<>(collection); } - public static List cloneAndSeal(Collection collection) { - if (collection == null) { - return Collections.emptyList(); - } - - ArrayList clone = newListWith(collection); - return Collections.unmodifiableList(clone); - } - - public static Map cloneAndSeal(Map map) { - Map clone = new HashMap<>(map); - return Collections.unmodifiableMap(clone); - } - public static Set newSetWith(Collection collection) { if (collection == null) { return null; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/EqualsUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/EqualsUtil.java index 8110358a7..9d786cc8f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/EqualsUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/EqualsUtil.java @@ -1,6 +1,6 @@ /** * - * Copyright 2019-2020 Florian Schmaus. + * Copyright 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. @@ -34,12 +34,6 @@ public final class EqualsUtil { return false; } - int thisHashCode = thisObject.hashCode(); - int otherHashCode = other.hashCode(); - if (thisHashCode != otherHashCode) { - return false; - } - EqualsUtil.Builder equalsBuilder = new EqualsUtil.Builder(); equalsComperator.compare(equalsBuilder, thisObjectClass.cast(other)); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketUtil.java index 59755660c..6124e66f4 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketUtil.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2014-2020 Florian Schmaus + * Copyright © 2014 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,40 @@ import org.jivesoftware.smack.packet.ExtensionElement; public class PacketUtil { + /** + * Get a extension element from a collection. + * @param collection TODO javadoc me please + * @param element TODO javadoc me please + * @param namespace TODO javadoc me please + * @param the type of the extension element. + * @return the extension element + * @deprecated use {@link #extensionElementFrom(Collection, String, String)} instead. + */ + @Deprecated + public static PE packetExtensionfromCollection( + Collection collection, String element, + String namespace) { + return extensionElementFrom(collection, element, namespace); + } + + /** + * Get a extension element from a collection. + * + * @param collection Collection of ExtensionElements. + * @param element name of the targeted ExtensionElement. + * @param namespace namespace of the targeted ExtensionElement. + * @param Type of the ExtensionElement + * + * @return the extension element + * @deprecated use {@link #extensionElementFrom(Collection, String, String)} instead + */ + @Deprecated + public static PE packetExtensionFromCollection( + Collection collection, String element, + String namespace) { + return extensionElementFrom(collection, element, namespace); + } + /** * Get a extension element from a collection. * diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java index c966b2d34..b2958395b 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java @@ -20,10 +20,8 @@ package org.jivesoftware.smack.util; import java.io.IOException; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.Collection; import java.util.Iterator; -import java.util.List; import java.util.Random; import java.util.regex.Pattern; @@ -593,11 +591,4 @@ public class StringUtils { } return appendable.append('\n'); } - - public static final String PORTABLE_NEWLINE_REGEX = "\\r?\\n"; - - public static List splitLinesPortable(String input) { - String[] lines = input.split(PORTABLE_NEWLINE_REGEX); - return Arrays.asList(lines); - } } 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 c483db913..145b1d3ec 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 @@ -77,10 +77,6 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element { .build(); } - public XmlEnvironment getXmlEnvironment() { - return effectiveXmlEnvironment; - } - public XmlStringBuilder escapedElement(String name, String escapedContent) { assert escapedContent != null; openElement(name); @@ -497,13 +493,6 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element { return this; } - public XmlStringBuilder optAppend(Collection elements) { - if (elements != null) { - append(elements); - } - return this; - } - public XmlStringBuilder optTextChild(CharSequence sqc, NamedElement parentElement) { if (sqc == null) { return closeEmptyElement(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java index 34ccb0d81..336e1e21e 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmppElementUtil.java @@ -49,7 +49,7 @@ public class XmppElementUtil { namespace = (String) fullyQualifiedElement.getField("NAMESPACE").get(null); } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) { - throw new IllegalArgumentException("The class" + fullyQualifiedElement + " has no ELEMENT, NAMESPACE or QNAME member. Consider adding QNAME", e); + throw new IllegalArgumentException("The class" + fullyQualifiedElement + " has no ELEMENT, NAMSEPACE or QNAME member. Consider adding QNAME", e); } return new QName(namespace, element); diff --git a/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java b/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java index a41c9ab78..513e28663 100644 --- a/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java +++ b/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java @@ -143,7 +143,6 @@ public final class EnhancedDebuggerWindow { debugger.tabbedPane.setName("XMPPConnection_" + tabbedPane.getComponentCount()); tabbedPane.add(debugger.tabbedPane, tabbedPane.getComponentCount() - 1); tabbedPane.setIconAt(tabbedPane.indexOfComponent(debugger.tabbedPane), connectionCreatedIcon); - tabbedPane.setSelectedIndex(tabbedPane.indexOfComponent(debugger.tabbedPane)); frame.setTitle( "Smack Debug Window -- Total connections: " + (tabbedPane.getComponentCount() - 1)); // Keep the added debugger for later access diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java index 3398df23c..9bbbe301e 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java @@ -278,9 +278,8 @@ public final class MamManager extends Manager { if (dataForm != null) { return dataForm; } - DataForm.Builder dataFormBuilder = getNewMamForm(); - dataFormBuilder.addFields(formFields.values()); - dataForm = dataFormBuilder.build(); + dataForm = getNewMamForm(); + dataForm.addFields(formFields.values()); return dataForm; } @@ -331,7 +330,7 @@ public final class MamManager extends Manager { } FormField formField = getWithFormField(withJid); - formFields.put(formField.getFieldName(), formField); + formFields.put(formField.getVariable(), formField); return this; } @@ -342,9 +341,9 @@ public final class MamManager extends Manager { } FormField formField = FormField.builder(FORM_FIELD_START) - .setValue(start) + .addValue(start) .build(); - formFields.put(formField.getFieldName(), formField); + formFields.put(formField.getVariable(), formField); FormField endFormField = formFields.get(FORM_FIELD_END); if (endFormField != null) { @@ -370,9 +369,9 @@ public final class MamManager extends Manager { } FormField formField = FormField.builder(FORM_FIELD_END) - .setValue(end) + .addValue(end) .build(); - formFields.put(formField.getFieldName(), formField); + formFields.put(formField.getVariable(), formField); FormField startFormField = formFields.get(FORM_FIELD_START); if (startFormField != null) { @@ -419,7 +418,7 @@ public final class MamManager extends Manager { } public Builder withAdditionalFormField(FormField formField) { - formFields.put(formField.getFieldName(), formField); + formFields.put(formField.getVariable(), formField); return this; } @@ -484,7 +483,7 @@ public final class MamManager extends Manager { private static FormField getWithFormField(Jid withJid) { return FormField.builder(FORM_FIELD_WITH) - .setValue(withJid.toString()) + .addValue(withJid.toString()) .build(); } @@ -719,9 +718,9 @@ public final class MamManager extends Manager { throw new SmackException.FeatureNotSupportedException(ADVANCED_CONFIG_NODE, archiveAddress); } - private static DataForm.Builder getNewMamForm() { - FormField field = FormField.buildHiddenFormType(MamElements.NAMESPACE); - DataForm.Builder form = DataForm.builder(); + private static DataForm getNewMamForm() { + FormField field = FormField.hiddenFormType(MamElements.NAMESPACE); + DataForm form = new DataForm(DataForm.Type.submit); form.addField(field); return form; } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java index b41400505..787de8a37 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2016-2020 Florian Schmaus and Fernando Ramirez + * Copyright © 2016 Florian Schmaus and Fernando Ramirez * * 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,7 @@ package org.jivesoftware.smackx.mam.element; import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.packet.DataForm; /** @@ -87,11 +88,11 @@ public class MamQueryIQ extends IQ { this.dataForm = dataForm; if (dataForm != null) { - String formType = dataForm.getFormType(); - if (formType == null) { + FormField field = dataForm.getHiddenFormTypeField(); + if (field == null) { throw new IllegalArgumentException("If a data form is given it must posses a hidden form type field"); } - if (!formType.equals(MamElements.NAMESPACE)) { + if (!field.getValues().get(0).toString().equals(MamElements.NAMESPACE)) { throw new IllegalArgumentException( "Value of the hidden form type field must be '" + MamElements.NAMESPACE + "'"); } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/MessageFasteningManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/MessageFasteningManager.java deleted file mode 100644 index d66cd1bff..000000000 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/MessageFasteningManager.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * - * Copyright 2019 Paul Schaub - * - * 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.smackx.message_fastening; - -import java.util.List; -import java.util.WeakHashMap; - -import org.jivesoftware.smack.ConnectionCreationListener; -import org.jivesoftware.smack.Manager; -import org.jivesoftware.smack.XMPPConnection; -import org.jivesoftware.smack.XMPPConnectionRegistry; -import org.jivesoftware.smack.packet.MessageBuilder; -import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; -import org.jivesoftware.smackx.message_fastening.element.FasteningElement; - -/** - * Smacks API for XEP-0422: Message Fastening. - * The API is still very bare bones, as the XEP intends Message Fastening to be used as a tool by other protocols. - * - * To enable / disable auto-announcing support for this feature, call {@link #setEnabledByDefault(boolean)} (default true). - * - * To fasten a payload to a previous message, create an {@link FasteningElement} using the builder provided by - * {@link FasteningElement#builder()}. - * - * You need to provide the {@link org.jivesoftware.smackx.sid.element.OriginIdElement} of the message you want to reference. - * Then add wrapped payloads using {@link FasteningElement.Builder#addWrappedPayloads(List)} - * and external payloads using {@link FasteningElement.Builder#addExternalPayloads(List)}. - * - * If you fastened some payloads onto the message previously and now want to replace the previous fastening, call - * {@link FasteningElement.Builder#isRemovingElement()}. - * Once you are finished, build the {@link FasteningElement} using {@link FasteningElement.Builder#build()} and add it to - * a stanza by calling {@link FasteningElement#applyTo(MessageBuilder)}. - * - * @see XEP-0422: Message Fastening - */ -public final class MessageFasteningManager extends Manager { - - public static final String NAMESPACE = "urn:xmpp:fasten:0"; - - private static boolean ENABLED_BY_DEFAULT = false; - - private static final WeakHashMap INSTANCES = new WeakHashMap<>(); - - static { - XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { - @Override - public void connectionCreated(XMPPConnection connection) { - if (ENABLED_BY_DEFAULT) { - MessageFasteningManager.getInstanceFor(connection).announceSupport(); - } - } - }); - } - - private MessageFasteningManager(XMPPConnection connection) { - super(connection); - } - - public static synchronized MessageFasteningManager getInstanceFor(XMPPConnection connection) { - MessageFasteningManager manager = INSTANCES.get(connection); - if (manager == null) { - manager = new MessageFasteningManager(connection); - INSTANCES.put(connection, manager); - } - return manager; - } - - /** - * Enable or disable auto-announcing support for Message Fastening. - * Default is enabled. - * - * @param enabled enabled - */ - public static synchronized void setEnabledByDefault(boolean enabled) { - ENABLED_BY_DEFAULT = enabled; - } - - /** - * Announce support for Message Fastening via Service Discovery. - */ - public void announceSupport() { - ServiceDiscoveryManager discoveryManager = ServiceDiscoveryManager.getInstanceFor(connection()); - discoveryManager.addFeature(NAMESPACE); - } - - /** - * Stop announcing support for Message Fastening. - */ - public void stopAnnouncingSupport() { - ServiceDiscoveryManager discoveryManager = ServiceDiscoveryManager.getInstanceFor(connection()); - discoveryManager.removeFeature(NAMESPACE); - } -} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/element/ExternalElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/element/ExternalElement.java deleted file mode 100644 index 2a83352db..000000000 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/element/ExternalElement.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * - * Copyright 2019 Paul Schaub - * - * 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.smackx.message_fastening.element; - -import org.jivesoftware.smack.packet.NamedElement; -import org.jivesoftware.smack.packet.XmlEnvironment; -import org.jivesoftware.smack.util.XmlStringBuilder; - -/** - * Child element of {@link FasteningElement}. - * Reference to a top level element in the stanza that contains the {@link FasteningElement}. - */ -public class ExternalElement implements NamedElement { - - public static final String ELEMENT = "external"; - public static final String ATTR_NAME = "name"; - public static final String ATTR_ELEMENT_NAMESPACE = "element-namespace"; - - private final String name; - private final String elementNamespace; - - /** - * Create a new {@link ExternalElement} that references a top level element with the given name. - * - * @param name name of the top level element - */ - public ExternalElement(String name) { - this(name, null); - } - - /** - * Create a new {@link ExternalElement} that references a top level element with the given name and namespace. - * - * @param name name of the top level element - * @param elementNamespace namespace of the top level element - */ - public ExternalElement(String name, String elementNamespace) { - this.name = name; - this.elementNamespace = elementNamespace; - } - - @Override - public String getElementName() { - return ELEMENT; - } - - @Override - public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { - XmlStringBuilder xml = new XmlStringBuilder(this); - xml.attribute(ATTR_NAME, getName()); - xml.optAttribute(ATTR_ELEMENT_NAMESPACE, getElementNamespace()); - return xml.closeEmptyElement(); - } - - /** - * Name of the referenced top level element, eg. 'body'. - * @return element name - */ - public String getName() { - return name; - } - - /** - * Namespace of the referenced top level element, eg. 'urn:example:lik'. - * @return element namespace - */ - public String getElementNamespace() { - return elementNamespace; - } -} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/element/FasteningElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/element/FasteningElement.java deleted file mode 100644 index 5328bd0b2..000000000 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/element/FasteningElement.java +++ /dev/null @@ -1,325 +0,0 @@ -/** - * - * Copyright 2019 Paul Schaub - * - * 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.smackx.message_fastening.element; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.jivesoftware.smack.packet.ExtensionElement; -import org.jivesoftware.smack.packet.Message; -import org.jivesoftware.smack.packet.MessageBuilder; -import org.jivesoftware.smack.packet.Stanza; -import org.jivesoftware.smack.packet.XmlEnvironment; -import org.jivesoftware.smack.util.Objects; -import org.jivesoftware.smack.util.XmlStringBuilder; -import org.jivesoftware.smackx.message_fastening.MessageFasteningManager; -import org.jivesoftware.smackx.sid.element.OriginIdElement; - -/** - * Message Fastening container element. - */ -public final class FasteningElement implements ExtensionElement { - - public static final String ELEMENT = "apply-to"; - public static final String NAMESPACE = MessageFasteningManager.NAMESPACE; - public static final String ATTR_ID = "id"; - public static final String ATTR_CLEAR = "clear"; - public static final String ATTR_SHELL = "shell"; - - private final OriginIdElement referencedStanzasOriginId; - private final List externalPayloads = new ArrayList<>(); - private final List wrappedPayloads = new ArrayList<>(); - private final boolean clear; - private final boolean shell; - - private FasteningElement(OriginIdElement originId, - List wrappedPayloads, - List externalPayloads, - boolean clear, - boolean shell) { - this.referencedStanzasOriginId = Objects.requireNonNull(originId, "Fastening element MUST contain an origin-id."); - this.wrappedPayloads.addAll(wrappedPayloads); - this.externalPayloads.addAll(externalPayloads); - this.clear = clear; - this.shell = shell; - } - - /** - * Return the {@link OriginIdElement origin-id} of the {@link Stanza} that the message fastenings are to be - * applied to. - * - * @return origin id of the referenced stanza - */ - public OriginIdElement getReferencedStanzasOriginId() { - return referencedStanzasOriginId; - } - - /** - * Return all wrapped payloads of this element. - * - * @see XEP-0422: §3.1. Wrapped Payloads - * - * @return wrapped payloads. - */ - public List getWrappedPayloads() { - return Collections.unmodifiableList(wrappedPayloads); - } - - /** - * Return all external payloads of this element. - * - * @see XEP-0422: §3.2. External Payloads - * - * @return external payloads. - */ - public List getExternalPayloads() { - return Collections.unmodifiableList(externalPayloads); - } - - /** - * Does this element remove a previously sent {@link FasteningElement}? - * - * @see - * XEP-0422: Message Fastening §3.4 Removing fastenings - * - * @return true if the clear attribute is set. - */ - public boolean isRemovingElement() { - return clear; - } - - /** - * Is this a shell element? - * Shell elements are otherwise empty elements that indicate that an encrypted payload of a message - * encrypted using XEP-420: Stanza Content Encryption contains a sensitive {@link FasteningElement}. - * - * @see - * XEP-0422: Message Fastening §3.5 Interaction with stanza encryption - * - * @return true if this is a shell element. - */ - public boolean isShellElement() { - return shell; - } - - /** - * Return true if the provided {@link Message} contains a {@link FasteningElement}. - * - * @param message message - * @return true if the stanza has an {@link FasteningElement}. - */ - public static boolean hasFasteningElement(Message message) { - return message.hasExtension(ELEMENT, MessageFasteningManager.NAMESPACE); - } - - /** - * Return true if the provided {@link MessageBuilder} contains a {@link FasteningElement}. - * - * @param builder message builder - * @return true if the stanza has an {@link FasteningElement}. - */ - public static boolean hasFasteningElement(MessageBuilder builder) { - return builder.hasExtension(FasteningElement.class); - } - - @Override - public String getNamespace() { - return MessageFasteningManager.NAMESPACE; - } - - @Override - public String getElementName() { - return ELEMENT; - } - - @Override - public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) { - XmlStringBuilder xml = new XmlStringBuilder(this) - .attribute(ATTR_ID, referencedStanzasOriginId.getId()) - .optBooleanAttribute(ATTR_CLEAR, isRemovingElement()) - .optBooleanAttribute(ATTR_SHELL, isShellElement()) - .rightAngleBracket(); - addPayloads(xml); - return xml.closeElement(this); - } - - private void addPayloads(XmlStringBuilder xml) { - for (ExternalElement external : externalPayloads) { - xml.append(external); - } - for (ExtensionElement wrapped : wrappedPayloads) { - xml.append(wrapped); - } - } - - public static FasteningElement createShellElementForSensitiveElement(FasteningElement sensitiveElement) { - return createShellElementForSensitiveElement(sensitiveElement.getReferencedStanzasOriginId()); - } - - public static FasteningElement createShellElementForSensitiveElement(String originIdOfSensitiveElement) { - return createShellElementForSensitiveElement(new OriginIdElement(originIdOfSensitiveElement)); - } - - public static FasteningElement createShellElementForSensitiveElement(OriginIdElement originIdOfSensitiveElement) { - return FasteningElement.builder() - .setOriginId(originIdOfSensitiveElement) - .setShell() - .build(); - } - - /** - * Add this element to the provided message builder. - * Note: The stanza MUST NOT contain more than one apply-to elements at the same time. - * - * @see XEP-0422 §4: Business Rules - * - * @param messageBuilder message builder - */ - public void applyTo(MessageBuilder messageBuilder) { - if (FasteningElement.hasFasteningElement(messageBuilder)) { - throw new IllegalArgumentException("Stanza cannot contain more than one apply-to elements."); - } else { - messageBuilder.addExtension(this); - } - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private OriginIdElement originId; - private final List wrappedPayloads = new ArrayList<>(); - private final List externalPayloads = new ArrayList<>(); - private boolean isClear = false; - private boolean isShell = false; - - /** - * Set the origin-id of the referenced message. - * - * @param originIdString origin id as String - * @return builder instance - */ - public Builder setOriginId(String originIdString) { - return setOriginId(new OriginIdElement(originIdString)); - } - - /** - * Set the {@link OriginIdElement} of the referenced message. - * - * @param originId origin-id as element - * @return builder instance - */ - public Builder setOriginId(OriginIdElement originId) { - this.originId = originId; - return this; - } - - /** - * Add a wrapped payload. - * - * @param wrappedPayload wrapped payload - * @return builder instance - */ - public Builder addWrappedPayload(ExtensionElement wrappedPayload) { - return addWrappedPayloads(Collections.singletonList(wrappedPayload)); - } - - /** - * Add multiple wrapped payloads at once. - * - * @param wrappedPayloads list of wrapped payloads - * @return builder instance - */ - public Builder addWrappedPayloads(List wrappedPayloads) { - this.wrappedPayloads.addAll(wrappedPayloads); - return this; - } - - /** - * Add an external payload. - * - * @param externalPayload external payload - * @return builder instance - */ - public Builder addExternalPayload(ExternalElement externalPayload) { - return addExternalPayloads(Collections.singletonList(externalPayload)); - } - - /** - * Add multiple external payloads at once. - * - * @param externalPayloads external payloads - * @return builder instance - */ - public Builder addExternalPayloads(List externalPayloads) { - this.externalPayloads.addAll(externalPayloads); - return this; - } - - /** - * Declare this {@link FasteningElement} to remove previous fastenings. - * Semantically the wrapped payloads of this element declares all wrapped payloads from the referenced - * fastening element that share qualified names as removed. - * - * @see - * XEP-0422: Message Fastening §3.4 Removing fastenings - * - * @return builder instance - */ - public Builder setClear() { - isClear = true; - return this; - } - - /** - * Declare this {@link FasteningElement} to be a shell element. - * Shell elements are used as hints that a Stanza Content Encryption payload contains another sensitive - * {@link FasteningElement}. The outer "shell" {@link FasteningElement} is used to do fastening collation. - * - * @see XEP-0422: Message Fastening §3.5 Interaction with stanza encryption - * @see XEP-0420: Stanza Content Encryption - * - * @return builder instance - */ - public Builder setShell() { - isShell = true; - return this; - } - - /** - * Build the element. - * @return built element. - */ - public FasteningElement build() { - validateThatIfIsShellThenOtherwiseEmpty(); - return new FasteningElement(originId, wrappedPayloads, externalPayloads, isClear, isShell); - } - - private void validateThatIfIsShellThenOtherwiseEmpty() { - if (!isShell) { - return; - } - - if (isClear || !wrappedPayloads.isEmpty() || !externalPayloads.isEmpty()) { - throw new IllegalArgumentException("A fastening that is a shell element must be otherwise empty " + - "and cannot have a 'clear' attribute."); - } - } - } -} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/element/package-info.java deleted file mode 100644 index 8ae0915c7..000000000 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/element/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * - * Copyright 2019 Paul Schaub - * - * 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. - */ - -/** - * XEP-0422: Message Fastening. - * - * @see XEP-0422: Message - * Fastening - * - */ -package org.jivesoftware.smackx.message_fastening.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/package-info.java deleted file mode 100644 index 90dba9915..000000000 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * - * Copyright 2019 Paul Schaub - * - * 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. - */ - -/** - * XEP-0422: Message Fastening. - * - * @see XEP-0422: Message - * Fastening - * - */ -package org.jivesoftware.smackx.message_fastening; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/provider/FasteningElementProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/provider/FasteningElementProvider.java deleted file mode 100644 index 7005bda99..000000000 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/provider/FasteningElementProvider.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * - * Copyright 2019 Paul Schaub - * - * 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.smackx.message_fastening.provider; - -import java.io.IOException; - -import org.jivesoftware.smack.packet.ExtensionElement; -import org.jivesoftware.smack.packet.XmlEnvironment; -import org.jivesoftware.smack.parsing.SmackParsingException; -import org.jivesoftware.smack.provider.ExtensionElementProvider; -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.smackx.message_fastening.MessageFasteningManager; -import org.jivesoftware.smackx.message_fastening.element.ExternalElement; -import org.jivesoftware.smackx.message_fastening.element.FasteningElement; - -public class FasteningElementProvider extends ExtensionElementProvider { - - public static final FasteningElementProvider TEST_INSTANCE = new FasteningElementProvider(); - - @Override - public FasteningElement parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException { - FasteningElement.Builder builder = FasteningElement.builder(); - builder.setOriginId(parser.getAttributeValue("", FasteningElement.ATTR_ID)); - if (ParserUtils.getBooleanAttribute(parser, FasteningElement.ATTR_CLEAR, false)) { - builder.setClear(); - } - if (ParserUtils.getBooleanAttribute(parser, FasteningElement.ATTR_SHELL, false)) { - builder.setShell(); - } - - outerloop: while (true) { - XmlPullParser.Event tag = parser.next(); - switch (tag) { - case START_ELEMENT: - String name = parser.getName(); - String namespace = parser.getNamespace(); - - // Parse external payload - if (MessageFasteningManager.NAMESPACE.equals(namespace) && ExternalElement.ELEMENT.equals(name)) { - ExternalElement external = new ExternalElement( - parser.getAttributeValue("", ExternalElement.ATTR_NAME), - parser.getAttributeValue("", ExternalElement.ATTR_ELEMENT_NAMESPACE)); - builder.addExternalPayload(external); - continue; - } - - // Parse wrapped payload - ExtensionElement wrappedPayload = PacketParserUtils.parseExtensionElement(name, namespace, parser, xmlEnvironment); - builder.addWrappedPayload(wrappedPayload); - break; - - case END_ELEMENT: - if (parser.getDepth() == initialDepth) { - break outerloop; - } - break; - default: - break; - } - } - return builder.build(); - } -} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/provider/package-info.java deleted file mode 100644 index cf2fcf1ff..000000000 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_fastening/provider/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * - * Copyright 2019 Paul Schaub - * - * 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. - */ - -/** - * XEP-0422: Message Fastening. - * - * @see XEP-0422: Message - * Fastening - * - */ -package org.jivesoftware.smackx.message_fastening.provider; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/push_notifications/element/EnablePushNotificationsIQ.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/push_notifications/element/EnablePushNotificationsIQ.java index 428b41b67..7c86584d8 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/push_notifications/element/EnablePushNotificationsIQ.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/push_notifications/element/EnablePushNotificationsIQ.java @@ -24,7 +24,6 @@ import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smackx.pubsub.packet.PubSub; import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.TextSingleFormField; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.Jid; @@ -99,23 +98,24 @@ public class EnablePushNotificationsIQ extends IQ { xml.rightAngleBracket(); if (publishOptions != null) { - DataForm.Builder dataForm = DataForm.builder(); + DataForm dataForm = new DataForm(DataForm.Type.submit); // TODO: Shouldn't this use some potentially existing PubSub API? Also FORM_TYPE fields are usually of type // 'hidden', but the examples in XEP-0357 do also not set the value to hidden and FORM_TYPE itself appears // to be more convention than specification. - FormField formTypeField = FormField.buildHiddenFormType(PubSub.NAMESPACE + "#publish-options"); - dataForm.addField(formTypeField); + FormField.Builder formTypeField = FormField.builder("FORM_TYPE"); + formTypeField.addValue(PubSub.NAMESPACE + "#publish-options"); + dataForm.addField(formTypeField.build()); Iterator> publishOptionsIterator = publishOptions.entrySet().iterator(); while (publishOptionsIterator.hasNext()) { Map.Entry pairVariableValue = publishOptionsIterator.next(); - TextSingleFormField.Builder field = FormField.builder(pairVariableValue.getKey()); - field.setValue(pairVariableValue.getValue()); + FormField.Builder field = FormField.builder(pairVariableValue.getKey()); + field.addValue(pairVariableValue.getValue()); dataForm.addField(field.build()); } - xml.append(dataForm.build()); + xml.append(dataForm); } return xml; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java index b157b40a0..585be47a6 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java @@ -101,25 +101,4 @@ public class OriginIdElement extends StableAndUniqueIdElement { .attribute(ATTR_ID, getId()) .closeEmptyElement(); } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (other == null) { - return false; - } - if (!(other instanceof OriginIdElement)) { - return false; - } - - OriginIdElement otherId = (OriginIdElement) other; - return getId().equals(otherId.getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } } diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers index d594578a4..81c36ee64 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers @@ -292,12 +292,6 @@ org.jivesoftware.smackx.dox.provider.DnsIqProvider - - - apply-to - urn:xmpp:fasten:0 - org.jivesoftware.smackx.message_fastening.provider.FasteningElementProvider - diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml index 6a4f6a759..347a698f3 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml @@ -8,6 +8,5 @@ org.jivesoftware.smackx.eme.ExplicitMessageEncryptionManager org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager org.jivesoftware.smackx.xmlelement.DataFormsXmlElementManager - org.jivesoftware.smackx.message_fastening.MessageFasteningManager diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java index 212c37e89..738906053 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016 Fernando Ramirez, 2018-2020 Florian Schmaus + * Copyright 2016 Fernando Ramirez, 2018 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 org.jxmpp.util.XmppDateTime; public class FiltersTest extends MamTest { private static String getMamXMemberWith(List fieldsNames, List fieldsValues) { - String xml = "" + "" + "" + String xml = "" + "" + "" + MamElements.NAMESPACE + "" + ""; for (int i = 0; i < fieldsNames.size() && i < fieldsValues.size(); i++) { diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamQueryIQProviderTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamQueryIQProviderTest.java index c9f3be99e..f39411a00 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamQueryIQProviderTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamQueryIQProviderTest.java @@ -85,7 +85,7 @@ public class MamQueryIQProviderTest { assertEquals(fields2.get(1).getType(), FormField.Type.jid_single); assertEquals(fields2.get(2).getType(), FormField.Type.text_single); assertEquals(fields2.get(2).getValues(), new ArrayList<>()); - assertEquals(fields2.get(4).getFieldName(), "urn:example:xmpp:free-text-search"); + assertEquals(fields2.get(4).getVariable(), "urn:example:xmpp:free-text-search"); } } diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java index 33e9df1a3..6dd2f8e73 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java @@ -49,8 +49,7 @@ public class MamTest extends SmackTestSuite { IllegalArgumentException, InvocationTargetException { Method methodGetNewMamForm = MamManager.class.getDeclaredMethod("getNewMamForm"); methodGetNewMamForm.setAccessible(true); - DataForm.Builder dataFormBuilder = (DataForm.Builder) methodGetNewMamForm.invoke(mamManager); - return dataFormBuilder.build(); + return (DataForm) methodGetNewMamForm.invoke(mamManager); } } diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java index 1f1450efd..d0919dbcf 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java @@ -30,7 +30,7 @@ import org.junit.jupiter.api.Test; public class PagingTest extends MamTest { private static final String pagingStanza = "" + "" - + "" + "" + + "" + "" + "urn:xmpp:mam:1" + "" + "" + "" + "10" + "" + "" + ""; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java index 8fe38eeec..bc4360eef 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java @@ -41,7 +41,7 @@ import org.jxmpp.jid.impl.JidCreate; public class QueryArchiveTest extends MamTest { private static final String mamSimpleQueryIQ = "" + "" - + "" + "" + "" + + "" + "" + "" + MamElements.NAMESPACE + "" + "" + "" + "" + ""; private static final String mamQueryResultExample = "" diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java index 157abc520..9adebb3d5 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016 Fernando Ramirez, 2018-2020 Florian Schmaus + * Copyright 2016 Fernando Ramirez, 2018-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. @@ -31,7 +31,7 @@ import org.junit.jupiter.api.Test; public class ResultsLimitTest extends MamTest { private static final String resultsLimitStanza = "" + "" - + "" + "" + "" + + "" + "" + "" + MamElements.NAMESPACE + "" + "" + "" + "" + "10" + "" + "" + ""; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java index 87000a6e8..264319c0e 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016 Fernando Ramirez, 2018-2020 Florian Schmaus + * Copyright 2016 Fernando Ramirez, 2018-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. @@ -28,17 +28,16 @@ import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.junit.jupiter.api.Test; -import org.jxmpp.jid.JidTestUtil; public class RetrieveFormFieldsTest extends MamTest { private static final String retrieveFormFieldStanza = "" + "" + ""; - private static final String additionalFieldsStanza = "" + "" + private static final String additionalFieldsStanza = "" + "" + "" + MamElements.NAMESPACE + "" + "" + "" + "Hi" + "" - + "" + "one@exampleone.org" + "" + + "" + "Hi2" + "" + ""; @Test @@ -52,11 +51,13 @@ public class RetrieveFormFieldsTest extends MamTest { @Test public void checkAddAdditionalFieldsStanza() throws Exception { FormField field1 = FormField.builder("urn:example:xmpp:free-text-search") - .setValue("Hi") + .setType(FormField.Type.text_single) + .addValue("Hi") .build(); - FormField field2 = FormField.jidSingleBuilder("urn:example:xmpp:stanza-content") - .setValue(JidTestUtil.BARE_JID_1) + FormField field2 = FormField.builder("urn:example:xmpp:stanza-content") + .setType(FormField.Type.jid_single) + .addValue("Hi2") .build(); MamQueryArgs mamQueryArgs = MamQueryArgs.builder() diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/message_fastening/MessageFasteningElementsTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/message_fastening/MessageFasteningElementsTest.java deleted file mode 100644 index 8228fccfb..000000000 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/message_fastening/MessageFasteningElementsTest.java +++ /dev/null @@ -1,229 +0,0 @@ -/** - * - * Copyright 2019 Paul Schaub - * - * 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.smackx.message_fastening; - -import static org.jivesoftware.smack.test.util.XmlUnitUtils.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; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.util.Arrays; - -import org.jivesoftware.smack.packet.MessageBuilder; -import org.jivesoftware.smack.packet.StandardExtensionElement; -import org.jivesoftware.smack.packet.StanzaFactory; -import org.jivesoftware.smack.packet.id.StandardStanzaIdSource; -import org.jivesoftware.smack.parsing.SmackParsingException; -import org.jivesoftware.smack.test.util.SmackTestUtil; -import org.jivesoftware.smack.test.util.TestUtils; -import org.jivesoftware.smack.xml.XmlPullParserException; -import org.jivesoftware.smackx.message_fastening.element.ExternalElement; -import org.jivesoftware.smackx.message_fastening.element.FasteningElement; -import org.jivesoftware.smackx.message_fastening.provider.FasteningElementProvider; -import org.jivesoftware.smackx.sid.element.OriginIdElement; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -public class MessageFasteningElementsTest { - - private final StanzaFactory stanzaFactory = new StanzaFactory(new StandardStanzaIdSource()); - - /** - * Test XML serialization of the {@link FasteningElement} using the example provided by - * the XEP. - * - * @see XEP-0422 §3.1 Wrapped Payloads - */ - @Test - public void fasteningElementSerializationTest() { - String xml = - "" + - " " + - ""; - - FasteningElement applyTo = FasteningElement.builder() - .setOriginId("origin-id-1") - .addWrappedPayload(new StandardExtensionElement("i-like-this", "urn:example:like")) - .build(); - - assertXmlSimilar(xml, applyTo.toXML().toString()); - } - - @ParameterizedTest - @EnumSource(SmackTestUtil.XmlPullParserKind.class) - public void fasteningDeserializationTest(SmackTestUtil.XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { - String xml = - "" + - " " + - " " + - " " + - ""; - - FasteningElement parsed = SmackTestUtil.parse(xml, FasteningElementProvider.class, parserKind); - - assertNotNull(parsed); - assertEquals(new OriginIdElement("origin-id-1"), parsed.getReferencedStanzasOriginId()); - assertFalse(parsed.isRemovingElement()); - assertFalse(parsed.isShellElement()); - - assertEquals(1, parsed.getWrappedPayloads().size()); - assertEquals("i-like-this", parsed.getWrappedPayloads().get(0).getElementName()); - assertEquals("urn:example:like", parsed.getWrappedPayloads().get(0).getNamespace()); - - assertEquals(2, parsed.getExternalPayloads().size()); - ExternalElement custom = parsed.getExternalPayloads().get(0); - assertEquals("custom", custom.getName()); - assertEquals("urn:example:custom", custom.getElementNamespace()); - ExternalElement body = parsed.getExternalPayloads().get(1); - assertEquals("body", body.getName()); - assertNull(body.getElementNamespace()); - } - - @Test - public void fasteningDeserializationClearTest() throws XmlPullParserException, IOException, SmackParsingException { - String xml = - "" + - " " + - ""; - - FasteningElement parsed = FasteningElementProvider.TEST_INSTANCE.parse(TestUtils.getParser(xml)); - - assertTrue(parsed.isRemovingElement()); - } - - @Test - public void fasteningElementWithExternalElementsTest() { - String xml = - "" + - " " + - " " + - " " + - ""; - - FasteningElement element = FasteningElement.builder() - .setOriginId("origin-id-2") - .addExternalPayloads(Arrays.asList( - new ExternalElement("body"), - new ExternalElement("custom", "urn:example:custom") - )) - .addWrappedPayload( - new StandardExtensionElement("edit", "urn:example.edit")) - .build(); - - assertXmlSimilar(xml, element.toXML().toString()); - } - - @Test - public void createShellElementSharesOriginIdTest() { - OriginIdElement originIdElement = new OriginIdElement("sensitive-stanza-1"); - FasteningElement sensitiveFastening = FasteningElement.builder() - .setOriginId(originIdElement) - .build(); - - FasteningElement shellElement = FasteningElement.createShellElementForSensitiveElement(sensitiveFastening); - - assertEquals(originIdElement, shellElement.getReferencedStanzasOriginId()); - } - - @Test - public void fasteningRemoveSerializationTest() { - String xml = - "" + - " Very much" + - ""; - - FasteningElement element = FasteningElement.builder() - .setOriginId("origin-id-1") - .setClear() - .addWrappedPayload(StandardExtensionElement.builder("i-like-this", "urn:example:like") - .setText("Very much") - .build()) - .build(); - - assertXmlSimilar(xml, element.toXML().toString()); - } - - @Test - public void hasFasteningElementTest() { - MessageBuilder messageBuilderWithFasteningElement = MessageBuilder.buildMessage() - .setBody("Hi!") - .addExtension(FasteningElement.builder().setOriginId("origin-id-1").build()); - MessageBuilder messageBuilderWithoutFasteningElement = MessageBuilder.buildMessage() - .setBody("Ho!"); - - assertTrue(FasteningElement.hasFasteningElement(messageBuilderWithFasteningElement)); - assertFalse(FasteningElement.hasFasteningElement(messageBuilderWithoutFasteningElement)); - } - - @Test - public void shellElementMustNotHaveClearAttributeTest() { - assertThrows(IllegalArgumentException.class, () -> - FasteningElement.builder() - .setShell() - .setClear() - .build()); - } - - @Test - public void shellElementMustNotContainAnyPayloads() { - assertThrows(IllegalArgumentException.class, () -> - FasteningElement.builder() - .setShell() - .addWrappedPayload(new StandardExtensionElement("edit", "urn:example.edit")) - .build()); - - assertThrows(IllegalArgumentException.class, () -> - FasteningElement.builder() - .setShell() - .addExternalPayload(new ExternalElement("body")) - .build()); - } - - @Test - public void ensureAddFasteningElementToStanzaWorks() { - MessageBuilder message = stanzaFactory.buildMessageStanza(); - FasteningElement fasteningElement = FasteningElement.builder().setOriginId("another-apply-to").build(); - - // Adding only one element is allowed - fasteningElement.applyTo(message); - } - - /** - * Ensure, that {@link FasteningElement#applyTo(MessageBuilder)} - * throws when trying to add an {@link FasteningElement} to a {@link MessageBuilder} that already contains one - * such element. - * - * @see XEP-0422: §4. Business Rules - */ - @Test - public void ensureStanzaCanOnlyContainOneFasteningElement() { - MessageBuilder messageWithFastening = stanzaFactory.buildMessageStanza(); - FasteningElement.builder().setOriginId("origin-id").build().applyTo(messageWithFastening); - - // Adding a second fastening MUST result in exception - Assertions.assertThrows(IllegalArgumentException.class, () -> - FasteningElement.builder().setOriginId("another-apply-to").build() - .applyTo(messageWithFastening)); - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/ServiceAdministrationManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/ServiceAdministrationManager.java index 78ce687a6..a333b2027 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/ServiceAdministrationManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/ServiceAdministrationManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016-2020 Florian Schmaus + * Copyright 2016-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. @@ -29,7 +29,7 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smackx.commands.AdHocCommandManager; import org.jivesoftware.smackx.commands.RemoteCommand; -import org.jivesoftware.smackx.xdata.form.FillableForm; +import org.jivesoftware.smackx.xdata.Form; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.Jid; @@ -72,7 +72,7 @@ public class ServiceAdministrationManager extends Manager { RemoteCommand command = addUser(); command.execute(); - FillableForm answerForm = new FillableForm(command.getForm()); + Form answerForm = command.getForm().createAnswerForm(); answerForm.setAnswer("accountjid", userJid); answerForm.setAnswer("password", password); @@ -101,7 +101,7 @@ public class ServiceAdministrationManager extends Manager { RemoteCommand command = deleteUser(); command.execute(); - FillableForm answerForm = new FillableForm(command.getForm()); + Form answerForm = command.getForm().createAnswerForm(); answerForm.setAnswer("accountjids", jidsToDelete); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java index 5407d11e3..6591d7512 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2009 Jonas Ådahl, 2011-2020 Florian Schmaus + * Copyright © 2009 Jonas Ådahl, 2011-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. @@ -19,16 +19,13 @@ package org.jivesoftware.smackx.caps; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Queue; -import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.WeakHashMap; @@ -51,6 +48,7 @@ import org.jivesoftware.smack.filter.PresenceTypeFilter; import org.jivesoftware.smack.filter.StanzaExtensionFilter; import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.filter.StanzaTypeFilter; +import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.PresenceBuilder; @@ -550,7 +548,7 @@ public final class EntityCapsManager extends Manager { final List identities = new LinkedList<>(ServiceDiscoveryManager.getInstanceFor(connection).getIdentities()); sdm.setNodeInformationProvider(localNodeVer, new AbstractNodeInformationProvider() { List features = sdm.getFeatures(); - List packetExtensions = sdm.getExtendedInfo(); + List packetExtensions = sdm.getExtendedInfoAsList(); @Override public List getNodeFeatures() { return features; @@ -560,7 +558,7 @@ public final class EntityCapsManager extends Manager { return identities; } @Override - public List getNodePacketExtensions() { + public List getNodePacketExtensions() { return packetExtensions; } }); @@ -602,7 +600,7 @@ public final class EntityCapsManager extends Manager { return false; // step 3.5 check for well-formed packet extensions - if (!verifyPacketExtensions(info)) + if (verifyPacketExtensions(info)) return false; String calculatedVer = generateVerificationString(info, hash).version; @@ -614,29 +612,27 @@ public final class EntityCapsManager extends Manager { } /** - * Verify that the given discovery info is not ill-formed. * - * @param info the discovery info to verify. - * @return true if the stanza extensions is not ill-formed + * @param info TODO javadoc me please + * @return true if the stanza extensions is ill-formed */ - private static boolean verifyPacketExtensions(DiscoverInfo info) { - Set foundFormTypes = new HashSet<>(); - List dataForms = info.getExtensions(DataForm.class); - for (DataForm dataForm : dataForms) { - FormField formFieldTypeField = dataForm.getHiddenFormTypeField(); - if (formFieldTypeField == null) { - continue; - } - - String type = formFieldTypeField.getFirstValue(); - boolean noDuplicate = foundFormTypes.add(type); - if (!noDuplicate) { - // Ill-formed extension: duplicate forms (by form field type string). - return false; + protected static boolean verifyPacketExtensions(DiscoverInfo info) { + List foundFormTypes = new LinkedList<>(); + for (ExtensionElement pe : info.getExtensions()) { + if (pe.getNamespace().equals(DataForm.NAMESPACE)) { + DataForm df = (DataForm) pe; + for (FormField f : df.getFields()) { + if (f.getVariable().equals("FORM_TYPE")) { + for (FormField fft : foundFormTypes) { + if (f.equals(fft)) + return true; + } + foundFormTypes.add(f); + } + } } } - - return true; + return false; } protected static CapsVersionAndHash generateVerificationString(DiscoverInfoView discoverInfo) { @@ -668,6 +664,8 @@ public final class EntityCapsManager extends Manager { // be "broken" implementation in the wild, so we *always* transform to lowercase. hash = hash.toLowerCase(Locale.US); + DataForm extendedInfo = DataForm.from(discoverInfo); + // 1. Initialize an empty string S ('sb' in this method). StringBuilder sb = new StringBuilder(); // Use StringBuilder as we don't // need thread-safe StringBuffer @@ -707,47 +705,50 @@ public final class EntityCapsManager extends Manager { sb.append('<'); } - List extendedInfos = discoverInfo.getExtensions(DataForm.class); - for (DataForm extendedInfo : extendedInfos) { - if (!extendedInfo.hasHiddenFormTypeField()) { - // Only use the data form for calculation is it has a hidden FORM_TYPE field. - // See XEP-0115 5.4 step 3.f - continue; - } + // only use the data form for calculation is it has a hidden FORM_TYPE + // field + // see XEP-0115 5.4 step 3.6 + if (extendedInfo != null && extendedInfo.hasHiddenFormTypeField()) { + synchronized (extendedInfo) { + // 6. If the service discovery information response includes + // XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e., + // by the XML character data of the element). + SortedSet fs = new TreeSet<>(new Comparator() { + @Override + public int compare(FormField f1, FormField f2) { + return f1.getVariable().compareTo(f2.getVariable()); + } + }); - // 6. If the service discovery information response includes - // XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e., - // by the XML character data of the element). - SortedSet fs = new TreeSet<>(new Comparator() { - @Override - public int compare(FormField f1, FormField f2) { - return f1.getFieldName().compareTo(f2.getFieldName()); + FormField ft = null; + + for (FormField f : extendedInfo.getFields()) { + if (!f.getVariable().equals("FORM_TYPE")) { + fs.add(f); + } else { + ft = f; + } } - }); - for (FormField f : extendedInfo.getFields()) { - if (!f.getFieldName().equals("FORM_TYPE")) { - fs.add(f); + // Add FORM_TYPE values + if (ft != null) { + formFieldValuesToCaps(ft.getValues(), sb); } - } - // Add FORM_TYPE values - formFieldValuesToCaps(Collections.singletonList(extendedInfo.getFormType()), sb); - - // 7. 3. For each field other than FORM_TYPE: - // 1. Append the value of the "var" attribute, followed by the - // '<' character. - // 2. Sort values by the XML character data of the - // element. - // 3. For each element, append the XML character data, - // followed by the '<' character. - for (FormField f : fs) { - sb.append(f.getFieldName()); - sb.append('<'); - formFieldValuesToCaps(f.getValues(), sb); + // 7. 3. For each field other than FORM_TYPE: + // 1. Append the value of the "var" attribute, followed by the + // '<' character. + // 2. Sort values by the XML character data of the + // element. + // 3. For each element, append the XML character data, + // followed by the '<' character. + for (FormField f : fs) { + sb.append(f.getVariable()); + sb.append('<'); + formFieldValuesToCaps(f.getValues(), sb); + } } } - // 8. Ensure that S is encoded according to the UTF-8 encoding (RFC // 3269). // 9. Compute the verification string by hashing S using the algorithm @@ -766,7 +767,7 @@ public final class EntityCapsManager extends Manager { return new CapsVersionAndHash(version, hash); } - private static void formFieldValuesToCaps(List i, StringBuilder sb) { + private static void formFieldValuesToCaps(List i, StringBuilder sb) { SortedSet fvs = new TreeSet<>(); fvs.addAll(i); for (CharSequence fv : fvs) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommand.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommand.java index c7815fa8c..32f6eea41 100755 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommand.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommand.java @@ -24,8 +24,7 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.packet.StanzaError; import org.jivesoftware.smackx.commands.packet.AdHocCommandData; -import org.jivesoftware.smackx.xdata.form.FillableForm; -import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jivesoftware.smackx.xdata.Form; import org.jxmpp.jid.Jid; @@ -189,8 +188,13 @@ public abstract class AdHocCommand { * @return the form of the current stage to fill out or the result of the * execution. */ - public DataForm getForm() { - return data.getForm(); + public Form getForm() { + if (data.getForm() == null) { + return null; + } + else { + return new Form(data.getForm()); + } } /** @@ -201,8 +205,8 @@ public abstract class AdHocCommand { * @param form the form of the current stage to fill out or the result of the * execution. */ - protected void setForm(DataForm form) { - data.setForm(form); + protected void setForm(Form form) { + data.setForm(form.getDataFormToSend()); } /** @@ -230,7 +234,7 @@ public abstract class AdHocCommand { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public abstract void next(FillableForm response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; + public abstract void next(Form response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; /** * Completes the command execution with the information provided in the @@ -246,7 +250,7 @@ public abstract class AdHocCommand { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public abstract void complete(FillableForm response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; + public abstract void complete(Form response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; /** * Goes to the previous stage. The requester is asking to re-send the diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommandManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommandManager.java index 0f4614181..588ca2116 100755 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommandManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommandManager.java @@ -52,8 +52,7 @@ import org.jivesoftware.smackx.disco.AbstractNodeInformationProvider; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.packet.DiscoverInfo; import org.jivesoftware.smackx.disco.packet.DiscoverItems; -import org.jivesoftware.smackx.xdata.form.FillableForm; -import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jivesoftware.smackx.xdata.Form; import org.jxmpp.jid.Jid; @@ -463,8 +462,7 @@ public final class AdHocCommandManager extends Manager { if (Action.next.equals(action)) { command.incrementStage(); - DataForm dataForm = requestData.getForm(); - command.next(new FillableForm(dataForm)); + command.next(new Form(requestData.getForm())); if (command.isLastStage()) { // If it is the last stage then the command is // completed @@ -477,8 +475,7 @@ public final class AdHocCommandManager extends Manager { } else if (Action.complete.equals(action)) { command.incrementStage(); - DataForm dataForm = requestData.getForm(); - command.complete(new FillableForm(dataForm)); + command.complete(new Form(requestData.getForm())); response.setStatus(Status.completed); // Remove the completed session executingCommands.remove(sessionId); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/RemoteCommand.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/RemoteCommand.java index 58d8cc74e..f31e2a317 100755 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/RemoteCommand.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/RemoteCommand.java @@ -23,8 +23,7 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smackx.commands.packet.AdHocCommandData; -import org.jivesoftware.smackx.xdata.form.FillableForm; -import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jivesoftware.smackx.xdata.Form; import org.jxmpp.jid.Jid; @@ -81,8 +80,8 @@ public class RemoteCommand extends AdHocCommand { } @Override - public void complete(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - executeAction(Action.complete, form.getDataFormToSubmit()); + public void complete(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + executeAction(Action.complete, form); } @Override @@ -101,13 +100,13 @@ public class RemoteCommand extends AdHocCommand { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public void execute(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - executeAction(Action.execute, form.getDataFormToSubmit()); + public void execute(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + executeAction(Action.execute, form); } @Override - public void next(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - executeAction(Action.next, form.getDataFormToSubmit()); + public void next(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + executeAction(Action.next, form); } @Override @@ -131,7 +130,7 @@ public class RemoteCommand extends AdHocCommand { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - private void executeAction(Action action, DataForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + private void executeAction(Action action, Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { // TODO: Check that all the required fields of the form were filled, if // TODO: not throw the corresponding exception. This will make a faster response, // TODO: since the request is stopped before it's sent. @@ -141,7 +140,10 @@ public class RemoteCommand extends AdHocCommand { data.setNode(getNode()); data.setSessionID(sessionID); data.setAction(action); - data.setForm(form); + + if (form != null) { + data.setForm(form.getDataFormToSend()); + } AdHocCommandData responseData = null; try { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/AbstractNodeInformationProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/AbstractNodeInformationProvider.java index cfd51526c..79b96a11b 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/AbstractNodeInformationProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/AbstractNodeInformationProvider.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2014-2020 Florian Schmaus + * Copyright © 2014 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ public abstract class AbstractNodeInformationProvider implements NodeInformation } @Override - public List getNodePacketExtensions() { + public List getNodePacketExtensions() { return null; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/NodeInformationProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/NodeInformationProvider.java index 95961d140..3b2587d2e 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/NodeInformationProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/NodeInformationProvider.java @@ -68,5 +68,5 @@ public interface NodeInformationProvider { * * @return a list of the stanza extensions defined in the node. */ - List getNodePacketExtensions(); + List getNodePacketExtensions(); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java index 84b529aad..a009d260b 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2018-2020 Florian Schmaus. + * Copyright 2003-2007 Jive Software, 2018-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. @@ -38,10 +38,10 @@ import org.jivesoftware.smack.XMPPConnectionRegistry; import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler; import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode; +import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.StanzaError; -import org.jivesoftware.smack.util.CollectionUtil; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.StringUtils; @@ -88,7 +88,7 @@ public final class ServiceDiscoveryManager extends Manager { private static final Map instances = new WeakHashMap<>(); private final Set features = new HashSet<>(); - private List extendedInfos = new ArrayList<>(2); + private DataForm extendedInfo = null; private final Map nodeInformationProviders = new ConcurrentHashMap<>(); // Create a new ServiceDiscoveryManager on every established connection @@ -307,8 +307,9 @@ public final class ServiceDiscoveryManager extends Manager { for (String feature : getFeatures()) { response.addFeature(feature); } - - response.addExtensions(extendedInfos); + if (extendedInfo != null) { + response.addExtension(extendedInfo); + } } /** @@ -426,59 +427,25 @@ public final class ServiceDiscoveryManager extends Manager { * configure the extended info before logging to the server so that the * information is already available if it is required upon login. * - * @param info the data form that contains the extend service discovery + * @param info TODO javadoc me please + * the data form that contains the extend service discovery * information. - * @deprecated use {@link #addExtendedInfo(DataForm)} instead. */ - // TODO: Remove in Smack 4.5 - @Deprecated public synchronized void setExtendedInfo(DataForm info) { - addExtendedInfo(info); + extendedInfo = info; + // Notify others of a state change of SDM. In order to keep the state consistent, this + // method is synchronized + renewEntityCapsVersion(); } /** - * Registers extended discovery information of this XMPP entity. When this - * client is queried for its information this data form will be returned as - * specified by XEP-0128. - *

+ * Returns the data form that is set as extended information for this Service Discovery instance (XEP-0128). * - * Since no stanza is actually sent to the server it is safe to perform this - * operation before logging to the server. In fact, you may want to - * configure the extended info before logging to the server so that the - * information is already available if it is required upon login. - * - * @param extendedInfo the data form that contains the extend service discovery information. - * @return the old data form which got replaced (if any) - * @since 4.4.0 + * @see XEP-128: Service Discovery Extensions + * @return the data form */ - public DataForm addExtendedInfo(DataForm extendedInfo) { - String formType = extendedInfo.getFormType(); - StringUtils.requireNotNullNorEmpty(formType, "The data form must have a form type set"); - - DataForm removedDataForm; - synchronized (this) { - removedDataForm = DataForm.remove(extendedInfos, formType); - - extendedInfos.add(extendedInfo); - - // Notify others of a state change of SDM. In order to keep the state consistent, this - // method is synchronized - renewEntityCapsVersion(); - } - return removedDataForm; - } - - /** - * Remove the extended discovery information of the given form type. - * - * @param formType the type of the data form with the extended discovery information to remove. - * @since 4.4.0 - */ - public synchronized void removeExtendedInfo(String formType) { - DataForm removedForm = DataForm.remove(extendedInfos, formType); - if (removedForm != null) { - renewEntityCapsVersion(); - } + public DataForm getExtendedInfo() { + return extendedInfo; } /** @@ -487,21 +454,13 @@ public final class ServiceDiscoveryManager extends Manager { * * @return the data form as List of PacketExtensions */ - public synchronized List getExtendedInfo() { - return CollectionUtil.newListWith(extendedInfos); - } - - /** - * Returns the data form as List of PacketExtensions, or null if no data form is set. - * This representation is needed by some classes (e.g. EntityCapsManager, NodeInformationProvider) - * - * @return the data form as List of PacketExtensions - * @deprecated use {@link #getExtendedInfo()} instead. - */ - // TODO: Remove in Smack 4.5 - @Deprecated - public List getExtendedInfoAsList() { - return getExtendedInfo(); + public List getExtendedInfoAsList() { + List res = null; + if (extendedInfo != null) { + res = new ArrayList<>(1); + res.add(extendedInfo); + } + return res; } /** @@ -512,13 +471,10 @@ public final class ServiceDiscoveryManager extends Manager { * operation before logging to the server. */ public synchronized void removeExtendedInfo() { - int extendedInfosCount = extendedInfos.size(); - extendedInfos.clear(); - if (extendedInfosCount > 0) { - // Notify others of a state change of SDM. In order to keep the state consistent, this - // method is synchronized - renewEntityCapsVersion(); - } + extendedInfo = null; + // Notify others of a state change of SDM. In order to keep the state consistent, this + // method is synchronized + renewEntityCapsVersion(); } /** diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FaultTolerantNegotiator.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FaultTolerantNegotiator.java new file mode 100644 index 000000000..5fc7bf6c3 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FaultTolerantNegotiator.java @@ -0,0 +1,111 @@ +/** + * + * Copyright 2003-2006 Jive Software. + * + * 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.smackx.filetransfer; + +import java.io.InputStream; +import java.io.OutputStream; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Stanza; + +import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; +import org.jivesoftware.smackx.si.packet.StreamInitiation; + +import org.jxmpp.jid.Jid; + + +/** + * The fault tolerant negotiator takes two stream negotiators, the primary and the secondary + * negotiator. If the primary negotiator fails during the stream negotiation process, the second + * negotiator is used. + */ +public class FaultTolerantNegotiator extends StreamNegotiator { + + private final StreamNegotiator primaryNegotiator; + private final StreamNegotiator secondaryNegotiator; + + public FaultTolerantNegotiator(XMPPConnection connection, StreamNegotiator primary, + StreamNegotiator secondary) { + super(connection); + this.primaryNegotiator = primary; + this.secondaryNegotiator = secondary; + } + + @Override + public void newStreamInitiation(Jid from, String streamID) { + primaryNegotiator.newStreamInitiation(from, streamID); + secondaryNegotiator.newStreamInitiation(from, streamID); + } + + @Override + InputStream negotiateIncomingStream(Stanza streamInitiation) { + throw new UnsupportedOperationException("Negotiation only handled by create incoming " + + "stream method."); + } + + @Override + public InputStream createIncomingStream(final StreamInitiation initiation) throws SmackException, XMPPErrorException, InterruptedException { + // This could be either an xep47 ibb 'open' iq or an xep65 streamhost iq + IQ initiationSet = initiateIncomingStream(connection(), initiation); + + StreamNegotiator streamNegotiator = determineNegotiator(initiationSet); + + return streamNegotiator.negotiateIncomingStream(initiationSet); + } + + private StreamNegotiator determineNegotiator(Stanza streamInitiation) { + if (streamInitiation instanceof Bytestream) { + return primaryNegotiator; + } else if (streamInitiation instanceof Open) { + return secondaryNegotiator; + } else { + throw new IllegalStateException("Unknown stream initiation type"); + } + } + + @Override + public OutputStream createOutgoingStream(String streamID, Jid initiator, Jid target) + throws SmackException, XMPPException, InterruptedException { + OutputStream stream; + try { + stream = primaryNegotiator.createOutgoingStream(streamID, initiator, target); + } + catch (Exception ex) { + stream = secondaryNegotiator.createOutgoingStream(streamID, initiator, target); + } + + return stream; + } + + @Override + public String[] getNamespaces() { + String[] primary = primaryNegotiator.getNamespaces(); + String[] secondary = secondaryNegotiator.getNamespaces(); + + String[] namespaces = new String[primary.length + secondary.length]; + System.arraycopy(primary, 0, namespaces, 0, primary.length); + System.arraycopy(secondary, 0, namespaces, primary.length, secondary.length); + + return namespaces; + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java index 9e670acb3..cb5783206 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java @@ -42,7 +42,6 @@ import org.jivesoftware.smackx.filetransfer.FileTransferException.NoAcceptableTr import org.jivesoftware.smackx.filetransfer.FileTransferException.NoStreamMethodsOfferedException; import org.jivesoftware.smackx.si.packet.StreamInitiation; import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.ListSingleFormField; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.Jid; @@ -190,7 +189,7 @@ public final class FileTransferNegotiator extends Manager { public StreamNegotiator selectStreamNegotiator( FileTransferRequest request) throws NotConnectedException, NoStreamMethodsOfferedException, NoAcceptableTransferMechanisms, InterruptedException { StreamInitiation si = request.getStreamInitiation(); - ListSingleFormField streamMethodField = getStreamMethodField(si + FormField streamMethodField = getStreamMethodField(si .getFeatureNegotiationForm()); if (streamMethodField == null) { @@ -217,11 +216,11 @@ public final class FileTransferNegotiator extends Manager { return selectedStreamNegotiator; } - private static ListSingleFormField getStreamMethodField(DataForm form) { - return (ListSingleFormField) form.getField(STREAM_DATA_FIELD_NAME); + private static FormField getStreamMethodField(DataForm form) { + return form.getField(STREAM_DATA_FIELD_NAME); } - private StreamNegotiator getNegotiator(final ListSingleFormField field) + private StreamNegotiator getNegotiator(final FormField field) throws NoAcceptableTransferMechanisms { String variable; boolean isByteStream = false; @@ -240,7 +239,12 @@ public final class FileTransferNegotiator extends Manager { throw new FileTransferException.NoAcceptableTransferMechanisms(); } - if (isByteStream) { + if (isByteStream && isIBB) { + return new FaultTolerantNegotiator(connection(), + byteStreamTransferManager, + inbandTransferManager); + } + else if (isByteStream) { return byteStreamTransferManager; } else { @@ -351,7 +355,11 @@ public final class FileTransferNegotiator extends Manager { throw new FileTransferException.NoAcceptableTransferMechanisms(); } - if (isByteStream) { + if (isByteStream && isIBB) { + return new FaultTolerantNegotiator(connection(), + byteStreamTransferManager, inbandTransferManager); + } + else if (isByteStream) { return byteStreamTransferManager; } else { @@ -360,15 +368,16 @@ public final class FileTransferNegotiator extends Manager { } private static DataForm createDefaultInitiationForm() { - DataForm.Builder form = DataForm.builder() - .setType(DataForm.Type.form); - ListSingleFormField.Builder fieldBuilder = FormField.listSingleBuilder(STREAM_DATA_FIELD_NAME); + DataForm form = new DataForm(DataForm.Type.form); + FormField.Builder fieldBuilder = FormField.builder(); + fieldBuilder.setFieldName(STREAM_DATA_FIELD_NAME) + .setType(FormField.Type.list_single); if (!IBB_ONLY) { fieldBuilder.addOption(Bytestream.NAMESPACE); } fieldBuilder.addOption(DataPacketExtension.NAMESPACE); form.addField(fieldBuilder.build()); - return form.build(); + return form; } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/IBBTransferNegotiator.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/IBBTransferNegotiator.java index 2ed45ae59..ed7c328c5 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/IBBTransferNegotiator.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/IBBTransferNegotiator.java @@ -90,8 +90,8 @@ public class IBBTransferNegotiator extends StreamNegotiator { } @Override - public String getNamespace() { - return DataPacketExtension.NAMESPACE; + public String[] getNamespaces() { + return new String[] { DataPacketExtension.NAMESPACE }; } @Override diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/Socks5TransferNegotiator.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/Socks5TransferNegotiator.java index 516d7c564..a29130469 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/Socks5TransferNegotiator.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/Socks5TransferNegotiator.java @@ -88,8 +88,8 @@ public class Socks5TransferNegotiator extends StreamNegotiator { } @Override - public String getNamespace() { - return Bytestream.NAMESPACE; + public String[] getNamespaces() { + return new String[] { Bytestream.NAMESPACE }; } @Override diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java index e41d2a38d..08031b6d3 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java @@ -33,7 +33,6 @@ import org.jivesoftware.smack.util.EventManger.Callback; import org.jivesoftware.smackx.si.packet.StreamInitiation; import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.ListSingleFormField; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.Jid; @@ -70,31 +69,33 @@ public abstract class StreamNegotiator extends Manager { * initiator. * * @param streamInitiationOffer The offer from the stream initiator to connect for a stream. - * @param namespace The namespace that relates to the accepted means of transfer. + * @param namespaces The namespace that relates to the accepted means of transfer. * @return The response to be forwarded to the initiator. */ protected static StreamInitiation createInitiationAccept( - StreamInitiation streamInitiationOffer, String namespace) { + StreamInitiation streamInitiationOffer, String[] namespaces) { StreamInitiation response = new StreamInitiation(); response.setTo(streamInitiationOffer.getFrom()); response.setFrom(streamInitiationOffer.getTo()); response.setType(IQ.Type.result); response.setStanzaId(streamInitiationOffer.getStanzaId()); - DataForm.Builder form = DataForm.builder(); - ListSingleFormField.Builder field = FormField.listSingleBuilder( + DataForm form = new DataForm(DataForm.Type.submit); + FormField.Builder field = FormField.builder( FileTransferNegotiator.STREAM_DATA_FIELD_NAME); - field.setValue(namespace); + for (String namespace : namespaces) { + field.addValue(namespace); + } form.addField(field.build()); - response.setFeatureNegotiationForm(form.build()); + response.setFeatureNegotiationForm(form); return response; } protected final IQ initiateIncomingStream(final XMPPConnection connection, StreamInitiation initiation) throws NoResponseException, XMPPErrorException, NotConnectedException { final StreamInitiation response = createInitiationAccept(initiation, - getNamespace()); + getNamespaces()); newStreamInitiation(initiation.getFrom(), initiation.getSessionID()); @@ -181,7 +182,7 @@ public abstract class StreamNegotiator extends Manager { * @return Returns the XMPP namespace reserved for this particular type of * file transfer. */ - public abstract String getNamespace(); + public abstract String[] getNamespaces(); public static void signal(String eventKey, IQ eventValue) { initationSetEvents.signalEvent(eventKey, eventValue); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java deleted file mode 100644 index 4941d236d..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java +++ /dev/null @@ -1,158 +0,0 @@ -/** - * - * 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.smackx.formtypes; - -import java.util.HashMap; -import java.util.Map; - -import org.jivesoftware.smack.util.Objects; - -import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.TextSingleFormField; -import org.jivesoftware.smackx.xdata.packet.DataForm; - -public class FormFieldRegistry { - - private static final Map> REGISTRY = new HashMap<>(); - - private static final Map LOOKASIDE_REGISTRY = new HashMap<>(); - - private static final Map FIELD_NAME_TO_FORM_TYPE = new HashMap<>(); - - static { - register(FormField.FORM_TYPE, FormField.Type.hidden); - } - - @SuppressWarnings("ReferenceEquality") - public static synchronized void register(DataForm dataForm) { - // TODO: Also allow forms of type 'result'? - if (dataForm.getType() != DataForm.Type.form) { - throw new IllegalArgumentException(); - } - - String formType = null; - TextSingleFormField hiddenFormTypeField = dataForm.getHiddenFormTypeField(); - if (hiddenFormTypeField != null) { - formType = hiddenFormTypeField.getValue(); - } - - for (FormField formField : dataForm.getFields()) { - // Note that we can compare here by reference equality to skip the hidden form type field. - if (formField == hiddenFormTypeField) { - continue; - } - - String fieldName = formField.getFieldName(); - FormField.Type type = formField.getType(); - register(formType, fieldName, type); - } - } - - public static synchronized void register(String formType, String fieldName, FormField.Type type) { - if (formType == null) { - FormFieldInformation formFieldInformation = lookup(fieldName); - if (formFieldInformation != null) { - if (Objects.equals(formType, formFieldInformation.formType) - && type.equals(formFieldInformation.formFieldType)) { - // The field is already registered, nothing to do here. - return; - } - - String message = "There is already a field with the name'" + fieldName - + "' registered with the field type '" + formFieldInformation.formFieldType - + "', while this tries to register the field with the type '" + type + '\''; - throw new IllegalArgumentException(message); - } - - LOOKASIDE_REGISTRY.put(fieldName, type); - return; - } - - Map fieldNameToType = REGISTRY.get(formType); - if (fieldNameToType == null) { - fieldNameToType = new HashMap<>(); - REGISTRY.put(formType, fieldNameToType); - } else { - FormField.Type previousType = fieldNameToType.get(fieldName); - if (previousType != null && previousType != type) { - throw new IllegalArgumentException(); - } - } - fieldNameToType.put(fieldName, type); - - FIELD_NAME_TO_FORM_TYPE.put(fieldName, formType); - } - - public static synchronized void register(String fieldName, FormField.Type type) { - FormField.Type previousType = LOOKASIDE_REGISTRY.get(fieldName); - if (previousType != null) { - if (previousType == type) { - // Nothing to do here. - return; - } - throw new IllegalArgumentException("There is already a field with the name '" + fieldName - + "' registered with type " + previousType - + ", while trying to register this field with type '" + type + "'"); - } - - LOOKASIDE_REGISTRY.put(fieldName, type); - } - - public static synchronized FormField.Type lookup(String formType, String fieldName) { - if (formType != null) { - Map fieldNameToTypeMap = REGISTRY.get(formType); - if (fieldNameToTypeMap != null) { - FormField.Type type = fieldNameToTypeMap.get(fieldName); - if (type != null) { - return type; - } - } - } else { - formType = FIELD_NAME_TO_FORM_TYPE.get(fieldName); - if (formType != null) { - FormField.Type type = lookup(formType, fieldName); - if (type != null) { - return type; - } - } - } - - // Fallback to lookaside registry. - return LOOKASIDE_REGISTRY.get(fieldName); - } - - public static synchronized FormFieldInformation lookup(String fieldName) { - String formType = FIELD_NAME_TO_FORM_TYPE.get(fieldName); - FormField.Type type = lookup(formType, fieldName); - if (type == null) { - return null; - } - - return new FormFieldInformation(type, formType); - } - - public static final class FormFieldInformation { - public final FormField.Type formFieldType; - public final String formType; - - - private FormFieldInformation(FormField.Type formFieldType, String formType) { - this.formFieldType = formFieldType; - this.formType = formType; - } - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/package-info.java deleted file mode 100644 index 4e6b2519a..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/package-info.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * - * 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. - */ - -/** - * Smack's implementation of XEP-0068: Field Standardization for Data Forms. - */ -package org.jivesoftware.smackx.formtypes; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatConstants.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationListener.java similarity index 63% rename from smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatConstants.java rename to smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationListener.java index ffc00a5a9..8b1259ec7 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatConstants.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationListener.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Florian Schmaus + * Copyright 2020 Aditya Borikar. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jivesoftware.smackx.muc; +package org.jivesoftware.smackx.geoloc; -public class MultiUserChatConstants { +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.geoloc.packet.GeoLocation; - public static final String NAMESPACE = "http://jabber.org/protocol/muc"; +import org.jxmpp.jid.BareJid; +public interface GeoLocationListener { + void onGeoLocationUpdated(BareJid jid, GeoLocation geoLocation, Message message); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationManager.java index f29306251..3dbee6589 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/GeoLocationManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015-2017 Ishan Khanna, Fernando Ramirez 2019-2020 Florian Schmaus + * Copyright 2015-2017 Ishan Khanna, Fernando Ramirez 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. @@ -16,9 +16,13 @@ */ package org.jivesoftware.smackx.geoloc; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import org.jivesoftware.smack.AsyncButOrdered; import org.jivesoftware.smack.ConnectionCreationListener; import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.SmackException.NoResponseException; @@ -26,26 +30,31 @@ import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnectionRegistry; import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; - +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.geoloc.packet.GeoLocation; import org.jivesoftware.smackx.geoloc.provider.GeoLocationProvider; -import org.jivesoftware.smackx.pep.PepEventListener; +import org.jivesoftware.smackx.pep.PepListener; import org.jivesoftware.smackx.pep.PepManager; +import org.jivesoftware.smackx.pubsub.EventElement; +import org.jivesoftware.smackx.pubsub.ItemsExtension; import org.jivesoftware.smackx.pubsub.PayloadItem; import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException; import org.jivesoftware.smackx.xdata.provider.FormFieldChildElementProviderManager; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.Jid; /** * Entry point for Smacks API for XEP-0080: User Location. *
- * To publish a UserLocation, please use {@link #publishGeoLocation(GeoLocation)} method. This will publish the node. + * To publish a UserLocation, please use {@link #sendGeolocation(GeoLocation)} method. This will publish the node. *
* To stop publishing a UserLocation, please use {@link #stopPublishingGeolocation()} method. This will send a disble publishing signal. *
- * To add a {@link PepEventListener} in order to remain updated with other users GeoLocation, use {@link #addGeoLocationListener(PepEventListener)} method. + * To add a {@link GeoLocationListener} in order to remain updated with other users GeoLocation, use {@link #addGeoLocationListener(GeoLocationListener)} method. *
* To link a GeoLocation with {@link Message}, use `message.addExtension(geoLocation)`. *
@@ -56,10 +65,16 @@ import org.jxmpp.jid.Jid; */ public final class GeoLocationManager extends Manager { - public static final String GEOLOCATION_NODE = GeoLocation.NAMESPACE; + public static final String GEOLOCATION_NODE = "http://jabber.org/protocol/geoloc"; + public static final String GEOLOCATION_NOTIFY = GEOLOCATION_NODE + "+notify"; private static final Map INSTANCES = new WeakHashMap<>(); + private static boolean ENABLE_USER_LOCATION_NOTIFICATIONS_BY_DEFAULT = true; + + private final Set geoLocationListeners = new CopyOnWriteArraySet<>(); + private final AsyncButOrdered asyncButOrdered = new AsyncButOrdered(); + private final ServiceDiscoveryManager serviceDiscoveryManager; private final PepManager pepManager; static { @@ -93,6 +108,31 @@ public final class GeoLocationManager extends Manager { private GeoLocationManager(XMPPConnection connection) { super(connection); pepManager = PepManager.getInstanceFor(connection); + pepManager.addPepListener(new PepListener() { + + @Override + public void eventReceived(EntityBareJid from, EventElement event, Message message) { + if (!GEOLOCATION_NODE.equals(event.getEvent().getNode())) { + return; + } + + final BareJid contact = from.asBareJid(); + asyncButOrdered.performAsyncButOrdered(contact, () -> { + ItemsExtension itemsExtension = (ItemsExtension) event.getEvent(); + List items = itemsExtension.getExtensions(); + @SuppressWarnings("unchecked") + PayloadItem payload = (PayloadItem) items.get(0); + GeoLocation geoLocation = payload.getPayload(); + for (GeoLocationListener listener : geoLocationListeners) { + listener.onGeoLocationUpdated(contact, geoLocation, message); + } + }); + } + }); + serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); + if (ENABLE_USER_LOCATION_NOTIFICATIONS_BY_DEFAULT) { + enableUserLocationNotifications(); + } } public void sendGeoLocationToJid(GeoLocation geoLocation, Jid jid) throws InterruptedException, @@ -120,18 +160,18 @@ public final class GeoLocationManager extends Manager { } /** - * Publish the user's geographic location through the Personal Eventing Protocol (PEP). + * Send geolocation through the PubSub node. * - * @param geoLocation the geographic location to publish. + * @param geoLocation TODO javadoc me please * @throws InterruptedException if the calling thread was interrupted. * @throws NotConnectedException if the XMPP connection is not connected. * @throws XMPPErrorException if there was an XMPP error returned. * @throws NoResponseException if there was no response from the remote entity. * @throws NotALeafNodeException if a PubSub leaf node operation was attempted on a non-leaf node. */ - public void publishGeoLocation(GeoLocation geoLocation) + public void sendGeolocation(GeoLocation geoLocation) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotALeafNodeException { - pepManager.publish(GEOLOCATION_NODE, new PayloadItem(geoLocation)); + pepManager.publish(GeoLocation.NAMESPACE, new PayloadItem(geoLocation)); } /** @@ -145,14 +185,25 @@ public final class GeoLocationManager extends Manager { */ public void stopPublishingGeolocation() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotALeafNodeException { - pepManager.publish(GEOLOCATION_NODE, new PayloadItem(GeoLocation.EMPTY_GEO_LOCATION)); + pepManager.publish(GeoLocation.NAMESPACE, new PayloadItem(GeoLocation.EMPTY_GEO_LOCATION)); } - public boolean addGeoLocationListener(PepEventListener listener) { - return pepManager.addPepEventListener(GEOLOCATION_NODE, GeoLocation.class, listener); + public static void setGeoLocationNotificationsEnabledByDefault(boolean bool) { + ENABLE_USER_LOCATION_NOTIFICATIONS_BY_DEFAULT = bool; } - public boolean removeGeoLocationListener(PepEventListener listener) { - return pepManager.removePepEventListener(listener); + public void enableUserLocationNotifications() { + serviceDiscoveryManager.addFeature(GEOLOCATION_NOTIFY); + } + + public void disableGeoLocationNotifications() { + serviceDiscoveryManager.removeFeature(GEOLOCATION_NOTIFY); + } + + public boolean addGeoLocationListener(GeoLocationListener geoLocationListener) { + return geoLocationListeners.add(geoLocationListener); + } + public boolean removeGeoLocationListener(GeoLocationListener geoLocationListener) { + return geoLocationListeners.remove(geoLocationListener); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/packet/GeoLocation.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/packet/GeoLocation.java index 79ba84de8..3cf633ee8 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/packet/GeoLocation.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/packet/GeoLocation.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015-2017 Ishan Khanna, Fernando Ramirez, 2019-2020 Florian Schmaus + * Copyright 2015-2017 Ishan Khanna, Fernando Ramirez, 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. @@ -19,13 +19,14 @@ package org.jivesoftware.smackx.geoloc.packet; import java.io.Serializable; import java.net.URI; import java.util.Date; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.xml.namespace.QName; import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; -import org.jivesoftware.smack.util.EqualsUtil; -import org.jivesoftware.smack.util.HashCode; +import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smackx.xdata.FormField; @@ -49,6 +50,8 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi public static final GeoLocation EMPTY_GEO_LOCATION = GeoLocation.builder().build(); + private static final Logger LOGGER = Logger.getLogger(GeoLocation.class.getName()); + private final Double accuracy; private final Double alt; private final Double altAccuracy; @@ -74,31 +77,50 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi private final String tzo; private final URI uri; - private GeoLocation(Builder builder) { - accuracy = builder.accuracy; - alt = builder.alt; - altAccuracy = builder.altAccuracy; - area = builder.area; - bearing = builder.bearing; - building = builder.building; - country = builder.country; - countryCode = builder.countryCode; - datum = builder.datum; - description = builder.description; - error = builder.error; - floor = builder.floor; - lat = builder.lat; - locality = builder.locality; - lon = builder.lon; - postalcode = builder.postalcode; - region = builder.region; - room = builder.room; - speed = builder.speed; - street = builder.street; - text = builder.text; - timestamp = builder.timestamp; - tzo = builder.tzo; - uri = builder.uri; + private GeoLocation(Double accuracy, Double alt, Double altAccuracy, String area, Double bearing, String building, String country, + String countryCode, String datum, String description, Double error, String floor, Double lat, + String locality, Double lon, String postalcode, String region, String room, Double speed, + String street, String text, Date timestamp, String tzo, URI uri) { + this.accuracy = accuracy; + this.alt = alt; + this.altAccuracy = altAccuracy; + this.area = area; + this.bearing = bearing; + this.building = building; + this.country = country; + this.countryCode = countryCode; + + // If datum is not included, receiver MUST assume WGS84; receivers MUST implement WGS84; senders MAY use another + // datum, but it is not recommended. + + if (StringUtils.isNullOrEmpty(datum)) { + datum = "WGS84"; + } + + this.datum = datum; + this.description = description; + + // error element is deprecated in favor of accuracy + if (accuracy != null) { + error = null; + LOGGER.log(Level.WARNING, + "Error and accuracy set. Ignoring error as it is deprecated in favor of accuracy"); + } + + this.error = error; + this.floor = floor; + this.lat = lat; + this.locality = locality; + this.lon = lon; + this.postalcode = postalcode; + this.region = region; + this.room = room; + this.speed = speed; + this.street = street; + this.text = text; + this.timestamp = timestamp; + this.tzo = tzo; + this.uri = uri; } public Double getAccuracy() { @@ -141,13 +163,6 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi return description; } - /** - * Get the error. - * - * @return the error. - * @deprecated use {@link #getAccuracy()} instead. - */ - @Deprecated public Double getError() { return error; } @@ -251,70 +266,6 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi return NAMESPACE; } - private final HashCode.Cache hashCodeCache = new HashCode.Cache(); - - @Override - public int hashCode() { - return hashCodeCache.getHashCode(c -> - c - .append(accuracy) - .append(alt) - .append(altAccuracy) - .append(area) - .append(bearing) - .append(building) - .append(country) - .append(countryCode) - .append(datum) - .append(description) - .append(error) - .append(floor) - .append(lat) - .append(locality) - .append(lon) - .append(postalcode) - .append(region) - .append(room) - .append(speed) - .append(street) - .append(text) - .append(timestamp) - .append(tzo) - .append(uri) - ); - } - - @Override - public boolean equals(Object obj) { - return EqualsUtil.equals(this, obj, (e, o) -> { - e - .append(accuracy, o.accuracy) - .append(altAccuracy, o.altAccuracy) - .append(area, o.area) - .append(bearing, o.bearing) - .append(building, o.building) - .append(country, o.country) - .append(countryCode, o.countryCode) - .append(datum, o.datum) - .append(description, o.description) - .append(error, o.error) - .append(floor, o.floor) - .append(lat, o.lat) - .append(locality, o.locality) - .append(lon, o.lon) - .append(postalcode, o.postalcode) - .append(region, o.region) - .append(room, o.room) - .append(speed, o.speed) - .append(street, o.street) - .append(text, o.text) - .append(timestamp, o.timestamp) - .append(tzo, o.tzo) - .append(uri, o.uri) - ; - }); - } - /** * Returns a new instance of {@link Builder}. * @return Builder @@ -367,11 +318,7 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi private String building; private String country; private String countryCode; - - // If datum is not included, receiver MUST assume WGS84; receivers MUST implement WGS84; senders MAY use another - // datum, but it is not recommended. - private String datum = "WGS84"; - + private String datum; private String description; private Double error; private String floor; @@ -506,9 +453,7 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi * * @param error error in arc minutes * @return Builder - * @deprecated use {@link #setAccuracy(Double)} instead. */ - @Deprecated public Builder setError(Double error) { this.error = error; return this; @@ -665,7 +610,10 @@ public final class GeoLocation implements Serializable, ExtensionElement, FormFi * @return GeoLocation */ public GeoLocation build() { - return new GeoLocation(this); + + return new GeoLocation(accuracy, alt, altAccuracy, area, bearing, building, country, countryCode, datum, description, + error, floor, lat, locality, lon, postalcode, region, room, speed, street, text, timestamp, + tzo, uri); } } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/provider/GeoLocationProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/provider/GeoLocationProvider.java index 08275293e..cf6234aa4 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/provider/GeoLocationProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/geoloc/provider/GeoLocationProvider.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015-2017 Ishan Khanna, Fernando Ramirez, 2019-2020 Florian Schmaus + * Copyright 2015-2017 Ishan Khanna, Fernando Ramirez, 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. @@ -79,7 +79,7 @@ public class GeoLocationProvider extends ExtensionElementProvider { builder.setDescription(parser.nextText()); break; case "error": - parseError(builder, parser); + builder.setError(ParserUtils.getDoubleFromNextText(parser)); break; case "floor": builder.setFloor(parser.nextText()); @@ -136,12 +136,6 @@ public class GeoLocationProvider extends ExtensionElementProvider { return builder.build(); } - @SuppressWarnings("deprecation") - private static void parseError(GeoLocation.Builder builder, XmlPullParser parser) throws XmlPullParserException, IOException { - double error = ParserUtils.getDoubleFromNextText(parser); - builder.setError(error); - } - public static class GeoLocationFormFieldChildElementProvider extends FormFieldChildElementProvider { public static final GeoLocationFormFieldChildElementProvider INSTANCE = new GeoLocationFormFieldChildElementProvider(); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodListener.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodListener.java index 145aa0b41..cc644bd07 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodListener.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodListener.java @@ -1,6 +1,6 @@ /** * - * Copyright 2018 Paul Schaub, 2020 Florian Schmaus. + * Copyright 2018 Paul Schaub. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,9 @@ import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smackx.mood.element.MoodElement; -import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.BareJid; public interface MoodListener { - void onMoodUpdated(EntityBareJid from, MoodElement moodElement, String id, Message message); - + void onMoodUpdated(BareJid jid, Message message, MoodElement moodElement); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodManager.java index 5261a45e8..27f270ae8 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2018 Paul Schaub, 2020 Florian Schmaus. + * Copyright 2018 Paul Schaub. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,12 @@ */ package org.jivesoftware.smackx.mood; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; +import org.jivesoftware.smack.AsyncButOrdered; import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; @@ -26,13 +29,21 @@ import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.mood.element.MoodConcretisation; import org.jivesoftware.smackx.mood.element.MoodElement; import org.jivesoftware.smackx.mood.provider.MoodConcretisationProvider; -import org.jivesoftware.smackx.pep.PepEventListener; +import org.jivesoftware.smackx.pep.PepListener; import org.jivesoftware.smackx.pep.PepManager; +import org.jivesoftware.smackx.pubsub.EventElement; +import org.jivesoftware.smackx.pubsub.ItemsExtension; +import org.jivesoftware.smackx.pubsub.LeafNode; import org.jivesoftware.smackx.pubsub.PayloadItem; import org.jivesoftware.smackx.pubsub.PubSubException; +import org.jivesoftware.smackx.pubsub.PubSubManager; + +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; /** * Entry point for Smacks API for XEP-0107: User Mood. @@ -40,8 +51,8 @@ import org.jivesoftware.smackx.pubsub.PubSubException; * To set a mood, please use one of the {@link #setMood(Mood)} methods. This will publish the users mood to a pubsub * node.
*
- * In order to get updated about other users moods, register a {@link PepEventListener} at - * {@link #addMoodListener(PepEventListener)}. That listener will get updated about any incoming mood updates of contacts.
+ * In order to get updated about other users moods, register a {@link MoodListener} at + * {@link #addMoodListener(MoodListener)}. That listener will get updated about any incoming mood updates of contacts.
*
* To stop publishing the users mood, refer to {@link #clearMood()}.
*
@@ -57,15 +68,39 @@ import org.jivesoftware.smackx.pubsub.PubSubException; public final class MoodManager extends Manager { public static final String MOOD_NODE = "http://jabber.org/protocol/mood"; + public static final String MOOD_NOTIFY = MOOD_NODE + "+notify"; private static final Map INSTANCES = new WeakHashMap<>(); - private final PepManager pepManager; + private final Set moodListeners = new HashSet<>(); + private final AsyncButOrdered asyncButOrdered = new AsyncButOrdered<>(); + private PubSubManager pubSubManager; private MoodManager(XMPPConnection connection) { super(connection); + ServiceDiscoveryManager.getInstanceFor(connection).addFeature(MOOD_NOTIFY); + PepManager.getInstanceFor(connection).addPepListener(new PepListener() { + @Override + public void eventReceived(final EntityBareJid from, final EventElement event, final Message message) { + if (!MOOD_NODE.equals(event.getEvent().getNode())) { + return; + } - pepManager = PepManager.getInstanceFor(connection); + final BareJid contact = from.asBareJid(); + asyncButOrdered.performAsyncButOrdered(contact, new Runnable() { + @Override + public void run() { + ItemsExtension items = (ItemsExtension) event.getExtensions().get(0); + PayloadItem payload = (PayloadItem) items.getItems().get(0); + MoodElement mood = (MoodElement) payload.getPayload(); + + for (MoodListener listener : moodListeners) { + listener.onMoodUpdated(contact, message, mood); + } + } + }); + } + }); } public static synchronized MoodManager getInstanceFor(XMPPConnection connection) { @@ -112,7 +147,12 @@ public final class MoodManager extends Manager { private void publishMood(MoodElement moodElement) throws SmackException.NotLoggedInException, InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { - pepManager.publish(MOOD_NODE, new PayloadItem<>(moodElement)); + if (pubSubManager == null) { + pubSubManager = PubSubManager.getInstanceFor(getAuthenticatedConnectionOrThrow(), connection().getUser().asBareJid()); + } + + LeafNode node = pubSubManager.getOrCreateLeafNode(MOOD_NODE); + node.publish(new PayloadItem<>(moodElement)); } private static MoodElement buildMood(Mood mood, MoodConcretisation concretisation, String text) { @@ -130,11 +170,11 @@ public final class MoodManager extends Manager { message.addExtension(element); } - public boolean addMoodListener(PepEventListener listener) { - return pepManager.addPepEventListener(MOOD_NODE, MoodElement.class, listener); + public synchronized void addMoodListener(MoodListener listener) { + moodListeners.add(listener); } - public boolean removeMoodListener(PepEventListener listener) { - return pepManager.removePepEventListener(listener); + public synchronized void removeMoodListener(MoodListener listener) { + moodListeners.remove(listener); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java index 8d941ee06..5a7adcc64 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015-2020 Florian Schmaus + * Copyright 2015 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,18 +23,17 @@ import java.util.List; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.muc.MultiUserChatException.MucConfigurationNotSupportedException; +import org.jivesoftware.smackx.xdata.Form; import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.form.FillableForm; -import org.jivesoftware.smackx.xdata.form.FilledForm; -import org.jivesoftware.smackx.xdata.form.Form; import org.jxmpp.jid.Jid; import org.jxmpp.jid.util.JidUtil; /** - * Multi-User Chat configuration form manager is used to fill out and submit a {@link FilledForm} used to + * Multi-User Chat configuration form manager is used to fill out and submit a {@link Form} used to * configure rooms. *

* Room configuration needs either be done right after the room is created and still locked. Or at @@ -44,17 +43,12 @@ import org.jxmpp.jid.util.JidUtil; *

*

* The manager may not provide all possible configuration options. If you want direct access to the - * configuration form, use {@link MultiUserChat#getConfigurationForm()} and - * {@link MultiUserChat#sendConfigurationForm(FillableForm)}. + * configuraiton form, use {@link MultiUserChat#getConfigurationForm()} and + * {@link MultiUserChat#sendConfigurationForm(Form)}. *

*/ public class MucConfigFormManager { - - private static final String HASH_ROOMCONFIG = "#roomconfig"; - - public static final String FORM_TYPE = MultiUserChatConstants.NAMESPACE + HASH_ROOMCONFIG; - - /** + /** * The constant String {@value}. * * @see XEP-0045 § 10. Owner Use Cases @@ -79,7 +73,7 @@ public class MucConfigFormManager { public static final String MUC_ROOMCONFIG_ROOMSECRET = "muc#roomconfig_roomsecret"; private final MultiUserChat multiUserChat; - private final FillableForm answerForm; + private final Form answerForm; private final List owners; /** @@ -100,13 +94,20 @@ public class MucConfigFormManager { // Set the answer form Form configForm = multiUserChat.getConfigurationForm(); - this.answerForm = configForm.getFillableForm(); + this.answerForm = configForm.createAnswerForm(); + // Add the default answers to the form to submit + for (FormField field : configForm.getFields()) { + if (field.getType() == FormField.Type.hidden + || StringUtils.isNullOrEmpty(field.getVariable())) { + continue; + } + answerForm.setDefaultAnswer(field.getVariable()); + } // Set the local variables according to the fields found in the answer form - FormField roomOwnersFormField = answerForm.getDataForm().getField(MUC_ROOMCONFIG_ROOMOWNERS); - if (roomOwnersFormField != null) { + if (answerForm.hasField(MUC_ROOMCONFIG_ROOMOWNERS)) { // Set 'owners' to the currently configured owners - List ownerStrings = roomOwnersFormField.getValues(); + List ownerStrings = answerForm.getField(MUC_ROOMCONFIG_ROOMOWNERS).getValues(); owners = new ArrayList<>(ownerStrings.size()); JidUtil.jidsFrom(ownerStrings, owners, null); } @@ -243,7 +244,7 @@ public class MucConfigFormManager { } /** - * Submit the configuration as {@link FilledForm} to the room. + * Submit the configuration as {@link Form} to the room. * * @throws NoResponseException if there was no response from the room. * @throws XMPPErrorException if there was an XMPP error returned. diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucEnterConfiguration.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucEnterConfiguration.java index de2b6606e..51b79fb57 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucEnterConfiguration.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucEnterConfiguration.java @@ -133,7 +133,7 @@ public final class MucEnterConfiguration { * * @param presenceBuilderConsumer a consumer which will be passed the presence build. * @return a reference to this builder. - * @since 4.4.0 + * @since 4.5 */ public Builder withPresence(Consumer presenceBuilderConsumer) { presenceBuilderConsumer.accept(joinPresenceBuilder); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index 422320bf1..747b1469f 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software. 2020 Florian Schmaus + * Copyright 2003-2007 Jive Software. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -75,10 +75,8 @@ import org.jivesoftware.smackx.muc.packet.MUCItem; import org.jivesoftware.smackx.muc.packet.MUCOwner; import org.jivesoftware.smackx.muc.packet.MUCUser; import org.jivesoftware.smackx.muc.packet.MUCUser.Status; +import org.jivesoftware.smackx.xdata.Form; import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.TextSingleFormField; -import org.jivesoftware.smackx.xdata.form.FillableForm; -import org.jivesoftware.smackx.xdata.form.Form; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.DomainBareJid; @@ -151,6 +149,7 @@ public class MultiUserChat { private String subject; private EntityFullJid myRoomJid; + private boolean joined = false; private StanzaCollector messageCollector; MultiUserChat(XMPPConnection connection, EntityBareJid room, MultiUserChatManager multiUserChatManager) { @@ -234,13 +233,10 @@ public class MultiUserChat { occupantsMap.remove(from); MUCUser mucUser = MUCUser.from(packet); if (mucUser != null && mucUser.hasStatus()) { - if (isUserStatusModification) { - userHasLeft(); - } // Fire events according to the received presence code checkPresenceCode( mucUser.getStatus(), - isUserStatusModification, + presence.getFrom().equals(myRoomJID), mucUser, from); } else { @@ -250,27 +246,6 @@ public class MultiUserChat { listener.left(from); } } - - Destroy destroy = mucUser.getDestroy(); - // The room has been destroyed. - if (destroy != null) { - EntityBareJid alternateMucJid = destroy.getJid(); - final MultiUserChat alternateMuc; - if (alternateMucJid == null) { - alternateMuc = null; - } else { - alternateMuc = multiUserChatManager.getMultiUserChat(alternateMucJid); - } - - for (UserStatusListener listener : userStatusListeners) { - listener.roomDestroyed(alternateMuc, destroy.getReason()); - } - } - } - if (isUserStatusModification) { - for (UserStatusListener listener : userStatusListeners) { - listener.removed(mucUser, presence); - } } break; default: @@ -408,6 +383,8 @@ public class MultiUserChat { Resourcepart receivedNickname = presence.getFrom().getResourceOrThrow(); setNickname(receivedNickname); + joined = true; + // Update the list of joined rooms multiUserChatManager.addJoinedRoom(room); return presence; @@ -459,7 +436,7 @@ public class MultiUserChat { public synchronized MucCreateConfigFormHandle create(Resourcepart nickname) throws NoResponseException, XMPPErrorException, InterruptedException, MucAlreadyJoinedException, NotConnectedException, MissingMucCreationAcknowledgeException, NotAMucServiceException { - if (isJoined()) { + if (joined) { throw new MucAlreadyJoinedException(); } @@ -514,7 +491,7 @@ public class MultiUserChat { */ public synchronized MucCreateConfigFormHandle createOrJoin(MucEnterConfiguration mucEnterConfiguration) throws NoResponseException, XMPPErrorException, InterruptedException, MucAlreadyJoinedException, NotConnectedException, NotAMucServiceException { - if (isJoined()) { + if (joined) { throw new MucAlreadyJoinedException(); } @@ -535,8 +512,8 @@ public class MultiUserChat { * instant room, use {@link #makeInstant()}. *

* For advanced configuration options, use {@link MultiUserChat#getConfigurationForm()}, get the answer form with - * {@link Form#getFillableForm()}, fill it out and send it back to the room with - * {@link MultiUserChat#sendConfigurationForm(FillableForm)}. + * {@link Form#createAnswerForm()}, fill it out and send it back to the room with + * {@link MultiUserChat#sendConfigurationForm(Form)}. *

*/ public class MucCreateConfigFormHandle { @@ -554,7 +531,7 @@ public class MultiUserChat { */ public void makeInstant() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - sendConfigurationForm(null); + sendConfigurationForm(new Form(DataForm.Type.submit)); } /** @@ -685,7 +662,7 @@ public class MultiUserChat { throws XMPPErrorException, NoResponseException, NotConnectedException, InterruptedException, NotAMucServiceException { // If we've already joined the room, leave it before joining under a new // nickname. - if (isJoined()) { + if (joined) { try { leaveSync(); } @@ -703,7 +680,7 @@ public class MultiUserChat { * @return true if currently in the multi user chat room. */ public boolean isJoined() { - return myRoomJid != null; + return joined; } /** @@ -739,6 +716,10 @@ public class MultiUserChat { // "if (!joined) return" because it should be always be possible to leave the room in case the instance's // state does not reflect the actual state. + // Reset occupant information first so that we are assume that we left the room even if sendStanza() would + // throw. + userHasLeft(); + final EntityFullJid myRoomJid = this.myRoomJid; if (myRoomJid == null) { throw new MucNotJoinedException(this); @@ -760,10 +741,6 @@ public class MultiUserChat { ) ); - // Reset occupant information first so that we are assume that we left the room even if sendStanza() would - // throw. - userHasLeft(); - Presence reflectedLeavePresence = connection.createStanzaCollectorAndSend(reflectedLeavePresenceFilter, leavePresence).nextResultOrThrow(); return reflectedLeavePresence; @@ -806,8 +783,7 @@ public class MultiUserChat { iq.setType(IQ.Type.get); IQ answer = connection.createStanzaCollectorAndSend(iq).nextResultOrThrow(); - DataForm dataForm = DataForm.from(answer, MucConfigFormManager.FORM_TYPE); - return new Form(dataForm); + return Form.getFormFrom(answer); } /** @@ -820,19 +796,11 @@ public class MultiUserChat { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public void sendConfigurationForm(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - final DataForm dataForm; - if (form != null) { - dataForm = form.getDataFormToSubmit(); - } else { - // Instant room, cf. XEP-0045 § 10.1.2 - dataForm = DataForm.builder().build(); - } - + public void sendConfigurationForm(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { MUCOwner iq = new MUCOwner(); iq.setTo(room); iq.setType(IQ.Type.set); - iq.addExtension(dataForm); + iq.addExtension(form.getDataFormToSend()); connection.createStanzaCollectorAndSend(iq).nextResultOrThrow(); } @@ -860,8 +828,7 @@ public class MultiUserChat { reg.setTo(room); IQ result = connection.createStanzaCollectorAndSend(reg).nextResultOrThrow(); - DataForm dataForm = DataForm.from(result); - return new Form(dataForm); + return Form.getFormFrom(result); } /** @@ -881,11 +848,11 @@ public class MultiUserChat { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public void sendRegistrationForm(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public void sendRegistrationForm(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { Registration reg = new Registration(); reg.setType(IQ.Type.set); reg.setTo(room); - reg.addExtension(form.getDataFormToSubmit()); + reg.addExtension(form.getDataFormToSend()); connection.createStanzaCollectorAndSend(reg).nextResultOrThrow(); } @@ -1168,7 +1135,7 @@ public class MultiUserChat { Objects.requireNonNull(nickname, "Nickname must not be null or blank."); // Check that we already have joined the room before attempting to change the // nickname. - if (!isJoined()) { + if (!joined) { throw new MucNotJoinedException(this); } final EntityFullJid jid = JidCreate.entityFullFrom(room, nickname); @@ -1211,6 +1178,11 @@ public class MultiUserChat { throw new MucNotJoinedException(this); } + // Check that we already have joined the room before attempting to change the + // availability status. + if (!joined) { + throw new MucNotJoinedException(this); + } // We change the availability status by sending a presence packet to the room with the // new presence status and mode Presence joinPresence = connection.getStanzaFactory().buildPresenceStanza() @@ -1259,17 +1231,19 @@ public class MultiUserChat { * @since 4.1 */ public void requestVoice() throws NotConnectedException, InterruptedException { - DataForm.Builder form = DataForm.builder() - .setFormType(MUCInitialPresence.NAMESPACE + "#request"); - - TextSingleFormField.Builder requestVoiceField = FormField.textSingleBuilder("muc#role"); + DataForm form = new DataForm(DataForm.Type.submit); + FormField.Builder formTypeField = FormField.builder(FormField.FORM_TYPE); + formTypeField.addValue(MUCInitialPresence.NAMESPACE + "#request"); + form.addField(formTypeField.build()); + FormField.Builder requestVoiceField = FormField.builder("muc#role"); + requestVoiceField.setType(FormField.Type.text_single); requestVoiceField.setLabel("Requested role"); - requestVoiceField.setValue("participant"); + requestVoiceField.addValue("participant"); form.addField(requestVoiceField.build()); Message message = connection.getStanzaFactory().buildMessageStanza() .to(room) - .addExtension(form.build()) + .addExtension(form) .build(); connection.sendStanza(message); } @@ -2145,7 +2119,7 @@ public class MultiUserChat { // to call leave() in order to resync the state. And leave() requires the nickname to send the unsubscribe // presence. occupantsMap.clear(); - myRoomJid = null; + joined = false; // Update the list of joined rooms multiUserChatManager.removeJoinedRoom(room); removeConnectionCallbacks(); @@ -2463,6 +2437,9 @@ public class MultiUserChat { if (statusCodes.contains(Status.KICKED_307)) { // Check if this occupant was kicked if (isUserModification) { + // Reset occupant information. + userHasLeft(); + for (UserStatusListener listener : userStatusListeners) { listener.kicked(mucUser.getItem().getActor(), mucUser.getItem().getReason()); } @@ -2477,9 +2454,15 @@ public class MultiUserChat { if (statusCodes.contains(Status.BANNED_301)) { // Check if this occupant was banned if (isUserModification) { + joined = false; for (UserStatusListener listener : userStatusListeners) { listener.banned(mucUser.getItem().getActor(), mucUser.getItem().getReason()); } + + // Reset occupant information. + occupantsMap.clear(); + myRoomJid = null; + userHasLeft(); } else { for (ParticipantStatusListener listener : participantStatusListeners) { @@ -2491,9 +2474,15 @@ public class MultiUserChat { if (statusCodes.contains(Status.REMOVED_AFFIL_CHANGE_321)) { // Check if this occupant's membership was revoked if (isUserModification) { + joined = false; for (UserStatusListener listener : userStatusListeners) { listener.membershipRevoked(); } + + // Reset occupant information. + occupantsMap.clear(); + myRoomJid = null; + userHasLeft(); } } // A occupant has changed his nickname in the room @@ -2502,6 +2491,18 @@ public class MultiUserChat { listener.nicknameChanged(from, mucUser.getItem().getNick()); } } + // The room has been destroyed. + if (mucUser.getDestroy() != null) { + MultiUserChat alternateMUC = multiUserChatManager.getMultiUserChat(mucUser.getDestroy().getJid()); + for (UserStatusListener listener : userStatusListeners) { + listener.roomDestroyed(alternateMUC, mucUser.getDestroy().getReason()); + } + + // Reset occupant information. + occupantsMap.clear(); + myRoomJid = null; + userHasLeft(); + } } /** diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/RoomInfo.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/RoomInfo.java index d321903dd..5c24b578e 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/RoomInfo.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/RoomInfo.java @@ -25,8 +25,8 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smackx.disco.packet.DiscoverInfo; +import org.jivesoftware.smackx.xdata.Form; import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.Jid; @@ -130,7 +130,7 @@ public class RoomInfo { /** * The rooms extended configuration form; */ - private final DataForm form; + private final Form form; RoomInfo(DiscoverInfo info) { final Jid from = info.getFrom(); @@ -166,7 +166,7 @@ public class RoomInfo { URL logs = null; String pubsub = null; // Get the information based on the discovered extended information - form = DataForm.from(info); + form = Form.getFormFrom(info); if (form != null) { FormField descField = form.getField("muc#roominfo_description"); if (descField != null && !descField.getValues().isEmpty()) { @@ -191,7 +191,7 @@ public class RoomInfo { FormField contactJidField = form.getField("muc#roominfo_contactjid"); if (contactJidField != null && !contactJidField.getValues().isEmpty()) { - List contactJidValues = contactJidField.getValues(); + List contactJidValues = contactJidField.getValues(); contactJid = JidUtil.filterEntityBareJidList(JidUtil.jidSetFrom(contactJidValues)); } @@ -420,7 +420,7 @@ public class RoomInfo { * href="http://xmpp.org/extensions/xep-0045.html#disco-roominfo">XEP-45: * Multi User Chat - 6.5 Querying for Room Information */ - public DataForm getForm() { + public Form getForm() { return form; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/UserStatusListener.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/UserStatusListener.java index 5e8614ed6..105a09a0d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/UserStatusListener.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/UserStatusListener.java @@ -17,21 +17,11 @@ package org.jivesoftware.smackx.muc; -import org.jivesoftware.smack.packet.Presence; - -import org.jivesoftware.smackx.muc.packet.MUCUser; - import org.jxmpp.jid.Jid; /** - * A listener that is fired anytime your participant's status in a room is changed, such as the user being kicked, - * banned, or granted admin permissions or the room is destroyed. - *

- * Note that the methods {@link #kicked(Jid, String)}, {@link #banned(Jid, String)} and - * {@link #roomDestroyed(MultiUserChat, String)} will be called before the generic {@link #removed(MUCUser, Presence)} - * callback will be invoked. The generic {@link #removed(MUCUser, Presence)} callback will be invoked every time the user - * was removed from the MUC involuntarily. It is hence the recommended callback to listen for and act upon. - *

+ * A listener that is fired anytime your participant's status in a room is changed, such as the + * user being kicked, banned, or granted admin permissions or the room is destroyed. * * @author Gaston Dombiak */ @@ -43,7 +33,6 @@ public interface UserStatusListener { * * @param actor the moderator that kicked your user from the room (e.g. user@host.org). * @param reason the reason provided by the actor to kick you from the room. - * @see #removed(MUCUser, Presence) */ void kicked(Jid actor, String reason); @@ -69,21 +58,10 @@ public interface UserStatusListener { * * @param actor the administrator that banned your user (e.g. user@host.org). * @param reason the reason provided by the administrator to banned you. - * @see #removed(MUCUser, Presence) */ void banned(Jid actor, String reason); - /** - * Called when a user is involuntarily removed from the room. - * - * @param mucUser the optional muc#user extension element - * @param presence the carrier presence - * @since 4.4.0 - */ - default void removed(MUCUser mucUser, Presence presence) { - }; - - /** + /** * Called when an administrator grants your user membership to the room. This means that you * will be able to join the members-only room. * @@ -150,7 +128,6 @@ public interface UserStatusListener { * * @param alternateMUC an alternate MultiUserChat, may be null. * @param reason the reason why the room was closed, may be null. - * @see #removed(MUCUser, Presence) */ void roomDestroyed(MultiUserChat alternateMUC, String reason); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/packet/MUCUser.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/packet/MUCUser.java index 81eda1319..87cd64ace 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/packet/MUCUser.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/packet/MUCUser.java @@ -410,7 +410,6 @@ public class MUCUser implements ExtensionElement { public static final Status NEW_NICKNAME_303 = Status.create(303); public static final Status KICKED_307 = Status.create(307); public static final Status REMOVED_AFFIL_CHANGE_321 = Status.create(321); - public static final Status REMOVED_FOR_TECHNICAL_REASONS_333 = Status.create(333); private final Integer code; @@ -420,14 +419,10 @@ public class MUCUser implements ExtensionElement { } public static Status create(Integer i) { - Status status; - // TODO: Use computeIfAbsent once Smack's minimum required Android SDK level is 24 or higher. - synchronized (statusMap) { - status = statusMap.get(i); - if (status == null) { - status = new Status(i); - statusMap.put(i, status); - } + Status status = statusMap.get(i); + if (status == null) { + status = new Status(i); + statusMap.put(i, status); } return status; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/offline/OfflineMessageManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/offline/OfflineMessageManager.java index 8021054da..4756c890d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/offline/OfflineMessageManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/offline/OfflineMessageManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2020 Florian Schmaus. + * Copyright 2003-2007 Jive Software. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ import org.jivesoftware.smackx.disco.packet.DiscoverInfo; import org.jivesoftware.smackx.disco.packet.DiscoverItems; import org.jivesoftware.smackx.offline.packet.OfflineMessageInfo; import org.jivesoftware.smackx.offline.packet.OfflineMessageRequest; -import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jivesoftware.smackx.xdata.Form; /** * The OfflineMessageManager helps manage offline messages even before the user has sent an @@ -115,12 +115,12 @@ public final class OfflineMessageManager extends Manager { */ public int getMessageCount() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { DiscoverInfo info = serviceDiscoveryManager.discoverInfo(null, namespace); - DataForm dataForm = DataForm.from(info, namespace); - if (dataForm == null) { - return 0; + Form extendedInfo = Form.getFormFrom(info); + if (extendedInfo != null) { + String value = extendedInfo.getField("number_of_messages").getFirstValue(); + return Integer.parseInt(value); } - String numberOfMessagesString = dataForm.getField("number_of_messages").getFirstValue(); - return Integer.parseInt(numberOfMessagesString); + return 0; } /** diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pep/PepManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pep/PepManager.java index 20b009287..b31a3d3e2 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pep/PepManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pep/PepManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2015-2020 Florian Schmaus + * Copyright 2003-2007 Jive Software, 2015-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. @@ -17,13 +17,10 @@ package org.jivesoftware.smackx.pep; -import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArraySet; -import java.util.logging.Logger; import org.jivesoftware.smack.AsyncButOrdered; import org.jivesoftware.smack.Manager; @@ -33,26 +30,20 @@ import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.filter.AndFilter; -import org.jivesoftware.smack.filter.MessageTypeFilter; import org.jivesoftware.smack.filter.StanzaFilter; +import org.jivesoftware.smack.filter.jidtype.AbstractJidTypeFilter.JidType; import org.jivesoftware.smack.filter.jidtype.FromJidTypeFilter; -import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; -import org.jivesoftware.smack.packet.NamedElement; import org.jivesoftware.smack.packet.Stanza; -import org.jivesoftware.smack.util.CollectionUtil; -import org.jivesoftware.smack.util.MultiMap; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.pubsub.EventElement; import org.jivesoftware.smackx.pubsub.Item; -import org.jivesoftware.smackx.pubsub.ItemsExtension; import org.jivesoftware.smackx.pubsub.LeafNode; -import org.jivesoftware.smackx.pubsub.PayloadItem; import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException; import org.jivesoftware.smackx.pubsub.PubSubFeature; import org.jivesoftware.smackx.pubsub.PubSubManager; -import org.jivesoftware.smackx.pubsub.filter.EventItemsExtensionFilter; +import org.jivesoftware.smackx.pubsub.filter.EventExtensionFilter; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; @@ -79,8 +70,6 @@ import org.jxmpp.jid.EntityBareJid; */ public final class PepManager extends Manager { - private static final Logger LOGGER = Logger.getLogger(PepManager.class.getName()); - private static final Map INSTANCES = new WeakHashMap<>(); public static synchronized PepManager getInstanceFor(XMPPConnection connection) { @@ -92,25 +81,16 @@ public final class PepManager extends Manager { return pepManager; } - // TODO: Ideally PepManager would re-use PubSubManager for this. But the functionality in PubSubManager does not yet - // exist. - private static final StanzaFilter PEP_EVENTS_FILTER = new AndFilter( - MessageTypeFilter.NORMAL_OR_HEADLINE, - FromJidTypeFilter.ENTITY_BARE_JID, - EventItemsExtensionFilter.INSTANCE); + private static final StanzaFilter FROM_BARE_JID_WITH_EVENT_EXTENSION_FILTER = new AndFilter( + new FromJidTypeFilter(JidType.BareJid), + EventExtensionFilter.INSTANCE); private final Set pepListeners = new CopyOnWriteArraySet<>(); private final AsyncButOrdered asyncButOrdered = new AsyncButOrdered<>(); - private final ServiceDiscoveryManager serviceDiscoveryManager; - private final PubSubManager pepPubSubManager; - private final MultiMap> pepEventListeners = new MultiMap<>(); - - private final Map, PepEventListenerCoupling> listenerToCouplingMap = new HashMap<>(); - /** * Creates a new PEP exchange manager. * @@ -118,10 +98,6 @@ public final class PepManager extends Manager { */ private PepManager(XMPPConnection connection) { super(connection); - - serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); - pepPubSubManager = PubSubManager.getInstanceFor(connection, null); - StanzaListener packetListener = new StanzaListener() { @Override public void processStanza(Stanza stanza) { @@ -130,118 +106,20 @@ public final class PepManager extends Manager { assert event != null; final EntityBareJid from = message.getFrom().asEntityBareJidIfPossible(); assert from != null; - asyncButOrdered.performAsyncButOrdered(from, new Runnable() { @Override public void run() { - ItemsExtension itemsExtension = (ItemsExtension) event.getEvent(); - String node = itemsExtension.getNode(); - for (PepListener listener : pepListeners) { listener.eventReceived(from, event, message); } - - List> nodeListeners; - synchronized (pepEventListeners) { - nodeListeners = pepEventListeners.getAll(node); - if (nodeListeners.isEmpty()) { - return; - } - - // Make a copy of the list. Note that it is important to do this within the synchronized - // block. - nodeListeners = CollectionUtil.newListWith(nodeListeners); - } - - for (PepEventListenerCoupling listener : nodeListeners) { - // TODO: Can there be more than one item? - List items = itemsExtension.getItems(); - for (NamedElement namedElementItem : items) { - Item item = (Item) namedElementItem; - String id = item.getId(); - @SuppressWarnings("unchecked") - PayloadItem payloadItem = (PayloadItem) item; - ExtensionElement payload = payloadItem.getPayload(); - - listener.invoke(from, payload, id, message); - } - } } }); } }; // TODO Add filter to check if from supports PubSub as per xep163 2 2.4 - connection.addSyncStanzaListener(packetListener, PEP_EVENTS_FILTER); - } + connection.addSyncStanzaListener(packetListener, FROM_BARE_JID_WITH_EVENT_EXTENSION_FILTER); - private static final class PepEventListenerCoupling { - private final String node; - private final Class extensionElementType; - private final PepEventListener pepEventListener; - - private PepEventListenerCoupling(String node, Class extensionElementType, - PepEventListener pepEventListener) { - this.node = node; - this.extensionElementType = extensionElementType; - this.pepEventListener = pepEventListener; - } - - private void invoke(EntityBareJid from, ExtensionElement payload, String id, Message carrierMessage) { - if (!extensionElementType.isInstance(payload)) { - LOGGER.warning("Ignoring " + payload + " from " + carrierMessage + " as it is not of type " - + extensionElementType); - return; - } - - E extensionElementPayload = extensionElementType.cast(payload); - pepEventListener.onPepEvent(from, extensionElementPayload, id, carrierMessage); - } - } - - public boolean addPepEventListener(String node, Class extensionElementType, - PepEventListener pepEventListener) { - PepEventListenerCoupling pepEventListenerCoupling = new PepEventListenerCoupling<>(node, - extensionElementType, pepEventListener); - - synchronized (pepEventListeners) { - if (listenerToCouplingMap.containsKey(pepEventListener)) { - return false; - } - listenerToCouplingMap.put(pepEventListener, pepEventListenerCoupling); - /* - * TODO: Replace the above with the below using putIfAbsent() if Smack's minimum required Android SDK level - * is 24 or higher. PepEventListenerCoupling currentPepEventListenerCoupling = - * listenerToCouplingMap.putIfAbsent(pepEventListener, pepEventListenerCoupling); if - * (currentPepEventListenerCoupling != null) { return false; } - */ - - boolean listenerForNodeExisted = pepEventListeners.put(node, pepEventListenerCoupling); - if (!listenerForNodeExisted) { - serviceDiscoveryManager.addFeature(node + PubSubManager.PLUS_NOTIFY); - } - } - return true; - } - - public boolean removePepEventListener(PepEventListener pepEventListener) { - synchronized (pepEventListeners) { - PepEventListenerCoupling pepEventListenerCoupling = listenerToCouplingMap.remove(pepEventListener); - if (pepEventListenerCoupling == null) { - return false; - } - - String node = pepEventListenerCoupling.node; - - boolean mappingExisted = pepEventListeners.removeOne(node, pepEventListenerCoupling); - assert mappingExisted; - - if (!pepEventListeners.containsKey(pepEventListenerCoupling.node)) { - // This was the last listener for the node. Remove the +notify feature. - serviceDiscoveryManager.removeFeature(node + PubSubManager.PLUS_NOTIFY); - } - } - - return true; + pepPubSubManager = PubSubManager.getInstanceFor(connection, null); } public PubSubManager getPepPubSubManager() { @@ -249,7 +127,8 @@ public final class PepManager extends Manager { } /** - * Adds a listener to PEPs. The listener will be fired anytime PEP events are received from remote XMPP clients. + * Adds a listener to PEPs. The listener will be fired anytime PEP events + * are received from remote XMPP clients. * * @param pepListener a roster exchange listener. * @return true if pepListener was added. @@ -297,8 +176,8 @@ public final class PepManager extends Manager { // @formatter:on }; - public boolean isSupported() - throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public boolean isSupported() throws NoResponseException, XMPPErrorException, + NotConnectedException, InterruptedException { XMPPConnection connection = connection(); ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); BareJid localBareJid = connection.getUser().asBareJid(); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java index 7f47847b5..db847372d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java @@ -22,8 +22,6 @@ import java.util.List; import org.jivesoftware.smack.packet.ExtensionElement; -import org.jivesoftware.smackx.pubsub.form.FilledConfigureForm; - /** * Represents the configuration element of a PubSub message event which * associates a configuration form to the node which was configured. The form @@ -32,18 +30,18 @@ import org.jivesoftware.smackx.pubsub.form.FilledConfigureForm; * @author Robin Collier */ public class ConfigurationEvent extends NodeExtension implements EmbeddedPacketExtension { - private final FilledConfigureForm form; + private ConfigureForm form; public ConfigurationEvent(String nodeId) { - this(nodeId, null); + super(PubSubElementType.CONFIGURATION, nodeId); } - public ConfigurationEvent(String nodeId, FilledConfigureForm configForm) { + public ConfigurationEvent(String nodeId, ConfigureForm configForm) { super(PubSubElementType.CONFIGURATION, nodeId); form = configForm; } - public FilledConfigureForm getConfiguration() { + public ConfigureForm getConfiguration() { return form; } @@ -52,6 +50,6 @@ public class ConfigurationEvent extends NodeExtension implements EmbeddedPacketE if (getConfiguration() == null) return Collections.emptyList(); else - return Arrays.asList((ExtensionElement) getConfiguration().getDataForm()); + return Arrays.asList((ExtensionElement) getConfiguration().getDataFormToSend()); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureForm.java new file mode 100644 index 000000000..91921eee7 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureForm.java @@ -0,0 +1,681 @@ +/** + * + * Copyright the original author or authors + * + * 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.smackx.pubsub; + +import java.util.ArrayList; +import java.util.List; + +import org.jivesoftware.smack.util.ParserUtils; + +import org.jivesoftware.smackx.xdata.Form; +import org.jivesoftware.smackx.xdata.FormField; +import org.jivesoftware.smackx.xdata.packet.DataForm; + +/** + * A decorator for a {@link Form} to easily enable reading and updating + * of node configuration. All operations read or update the underlying {@link DataForm}. + * + *

Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not + * exist, all ConfigureForm.setXXX methods will create the field in the wrapped form + * if it does not already exist. + * + * @author Robin Collier + */ +public class ConfigureForm extends Form { + /** + * Create a decorator from an existing {@link DataForm} that has been + * retrieved from parsing a node configuration request. + * + * @param configDataForm TODO javadoc me please + */ + public ConfigureForm(DataForm configDataForm) { + super(configDataForm); + } + + /** + * Create a decorator from an existing {@link Form} for node configuration. + * Typically, this can be used to create a decorator for an answer form + * by using the result of {@link #createAnswerForm()} as the input parameter. + * + * @param nodeConfigForm TODO javadoc me please + */ + public ConfigureForm(Form nodeConfigForm) { + super(nodeConfigForm.getDataFormToSend()); + } + + /** + * Create a new form for configuring a node. This would typically only be used + * when creating and configuring a node at the same time via {@link PubSubManager#createNode(String, Form)}, since + * configuration of an existing node is typically accomplished by calling {@link LeafNode#getNodeConfiguration()} and + * using the resulting form to create a answer form. See {@link #ConfigureForm(Form)}. + * @param formType TODO javadoc me please + */ + public ConfigureForm(DataForm.Type formType) { + super(formType); + } + + /** + * Get the currently configured {@link AccessModel}, null if it is not set. + * + * @return The current {@link AccessModel} + */ + public AccessModel getAccessModel() { + String value = getFieldValue(ConfigureNodeFields.access_model); + + if (value == null) + return null; + else + return AccessModel.valueOf(value); + } + + /** + * Sets the value of access model. + * + * @param accessModel TODO javadoc me please + */ + public void setAccessModel(AccessModel accessModel) { + addField(ConfigureNodeFields.access_model, FormField.Type.list_single); + setAnswer(ConfigureNodeFields.access_model.getFieldName(), getListSingle(accessModel.toString())); + } + + /** + * Returns the URL of an XSL transformation which can be applied to payloads in order to + * generate an appropriate message body element. + * + * @return URL to an XSL + */ + public String getBodyXSLT() { + return getFieldValue(ConfigureNodeFields.body_xslt); + } + + /** + * Set the URL of an XSL transformation which can be applied to payloads in order to + * generate an appropriate message body element. + * + * @param bodyXslt The URL of an XSL + */ + public void setBodyXSLT(String bodyXslt) { + addField(ConfigureNodeFields.body_xslt, FormField.Type.text_single); + setAnswer(ConfigureNodeFields.body_xslt.getFieldName(), bodyXslt); + } + + /** + * The id's of the child nodes associated with a collection node (both leaf and collection). + * + * @return list of child nodes. + */ + public List getChildren() { + return getFieldValues(ConfigureNodeFields.children); + } + + /** + * Set the list of child node ids that are associated with a collection node. + * + * @param children TODO javadoc me please + */ + public void setChildren(List children) { + addField(ConfigureNodeFields.children, FormField.Type.text_multi); + setAnswer(ConfigureNodeFields.children.getFieldName(), children); + } + + /** + * Returns the policy that determines who may associate children with the node. + * + * @return The current policy + */ + public ChildrenAssociationPolicy getChildrenAssociationPolicy() { + String value = getFieldValue(ConfigureNodeFields.children_association_policy); + + if (value == null) + return null; + else + return ChildrenAssociationPolicy.valueOf(value); + } + + /** + * Sets the policy that determines who may associate children with the node. + * + * @param policy The policy being set + */ + public void setChildrenAssociationPolicy(ChildrenAssociationPolicy policy) { + addField(ConfigureNodeFields.children_association_policy, FormField.Type.list_single); + List values = new ArrayList<>(1); + values.add(policy.toString()); + setAnswer(ConfigureNodeFields.children_association_policy.getFieldName(), values); + } + + /** + * List of JID's that are on the whitelist that determines who can associate child nodes + * with the collection node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to + * {@link ChildrenAssociationPolicy#whitelist}. + * + * @return List of the whitelist + */ + public List getChildrenAssociationWhitelist() { + return getFieldValues(ConfigureNodeFields.children_association_whitelist); + } + + /** + * Set the JID's in the whitelist of users that can associate child nodes with the collection + * node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to + * {@link ChildrenAssociationPolicy#whitelist}. + * + * @param whitelist The list of JID's + */ + public void setChildrenAssociationWhitelist(List whitelist) { + addField(ConfigureNodeFields.children_association_whitelist, FormField.Type.jid_multi); + setAnswer(ConfigureNodeFields.children_association_whitelist.getFieldName(), whitelist); + } + + /** + * Gets the maximum number of child nodes that can be associated with the collection node. + * + * @return The maximum number of child nodes + */ + public int getChildrenMax() { + return Integer.parseInt(getFieldValue(ConfigureNodeFields.children_max)); + } + + /** + * Set the maximum number of child nodes that can be associated with a collection node. + * + * @param max The maximum number of child nodes. + */ + public void setChildrenMax(int max) { + addField(ConfigureNodeFields.children_max, FormField.Type.text_single); + setAnswer(ConfigureNodeFields.children_max.getFieldName(), max); + } + + /** + * Gets the collection node which the node is affiliated with. + * + * @return The collection node id + */ + public String getCollection() { + return getFieldValue(ConfigureNodeFields.collection); + } + + /** + * Sets the collection node which the node is affiliated with. + * + * @param collection The node id of the collection node + */ + public void setCollection(String collection) { + addField(ConfigureNodeFields.collection, FormField.Type.text_single); + setAnswer(ConfigureNodeFields.collection.getFieldName(), collection); + } + + /** + * Gets the URL of an XSL transformation which can be applied to the payload + * format in order to generate a valid Data Forms result that the client could + * display using a generic Data Forms rendering engine. + * + * @return The URL of an XSL transformation + */ + public String getDataformXSLT() { + return getFieldValue(ConfigureNodeFields.dataform_xslt); + } + + /** + * Sets the URL of an XSL transformation which can be applied to the payload + * format in order to generate a valid Data Forms result that the client could + * display using a generic Data Forms rendering engine. + * + * @param url The URL of an XSL transformation + */ + public void setDataformXSLT(String url) { + addField(ConfigureNodeFields.dataform_xslt, FormField.Type.text_single); + setAnswer(ConfigureNodeFields.dataform_xslt.getFieldName(), url); + } + + /** + * Does the node deliver payloads with event notifications. + * + * @return true if it does, false otherwise + */ + public boolean isDeliverPayloads() { + return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.deliver_payloads)); + } + + /** + * Sets whether the node will deliver payloads with event notifications. + * + * @param deliver true if the payload will be delivered, false otherwise + */ + public void setDeliverPayloads(boolean deliver) { + addField(ConfigureNodeFields.deliver_payloads, FormField.Type.bool); + setAnswer(ConfigureNodeFields.deliver_payloads.getFieldName(), deliver); + } + + /** + * Determines who should get replies to items. + * + * @return Who should get the reply + */ + public ItemReply getItemReply() { + String value = getFieldValue(ConfigureNodeFields.itemreply); + + if (value == null) + return null; + else + return ItemReply.valueOf(value); + } + + /** + * Sets who should get the replies to items. + * + * @param reply Defines who should get the reply + */ + public void setItemReply(ItemReply reply) { + addField(ConfigureNodeFields.itemreply, FormField.Type.list_single); + setAnswer(ConfigureNodeFields.itemreply.getFieldName(), getListSingle(reply.toString())); + } + + /** + * Gets the maximum number of items to persisted to this node if {@link #isPersistItems()} is + * true. + * + * @return The maximum number of items to persist + */ + public int getMaxItems() { + return Integer.parseInt(getFieldValue(ConfigureNodeFields.max_items)); + } + + /** + * Set the maximum number of items to persisted to this node if {@link #isPersistItems()} is + * true. + * + * @param max The maximum number of items to persist + */ + public void setMaxItems(int max) { + addField(ConfigureNodeFields.max_items, FormField.Type.text_single); + setAnswer(ConfigureNodeFields.max_items.getFieldName(), max); + } + + /** + * Gets the maximum payload size in bytes. + * + * @return The maximum payload size + */ + public int getMaxPayloadSize() { + return Integer.parseInt(getFieldValue(ConfigureNodeFields.max_payload_size)); + } + + /** + * Sets the maximum payload size in bytes. + * + * @param max The maximum payload size + */ + public void setMaxPayloadSize(int max) { + addField(ConfigureNodeFields.max_payload_size, FormField.Type.text_single); + setAnswer(ConfigureNodeFields.max_payload_size.getFieldName(), max); + } + + /** + * Gets the node type. + * + * @return The node type + */ + public NodeType getNodeType() { + String value = getFieldValue(ConfigureNodeFields.node_type); + + if (value == null) + return null; + else + return NodeType.valueOf(value); + } + + /** + * Sets the node type. + * + * @param type The node type + */ + public void setNodeType(NodeType type) { + addField(ConfigureNodeFields.node_type, FormField.Type.list_single); + setAnswer(ConfigureNodeFields.node_type.getFieldName(), getListSingle(type.toString())); + } + + /** + * Determines if subscribers should be notified when the configuration changes. + * + * @return true if they should be notified, false otherwise + */ + public boolean isNotifyConfig() { + return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.notify_config)); + } + + /** + * Sets whether subscribers should be notified when the configuration changes. + * + * @param notify true if subscribers should be notified, false otherwise + */ + public void setNotifyConfig(boolean notify) { + addField(ConfigureNodeFields.notify_config, FormField.Type.bool); + setAnswer(ConfigureNodeFields.notify_config.getFieldName(), notify); + } + + /** + * Determines whether subscribers should be notified when the node is deleted. + * + * @return true if subscribers should be notified, false otherwise + */ + public boolean isNotifyDelete() { + return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.notify_delete)); + } + + /** + * Sets whether subscribers should be notified when the node is deleted. + * + * @param notify true if subscribers should be notified, false otherwise + */ + public void setNotifyDelete(boolean notify) { + addField(ConfigureNodeFields.notify_delete, FormField.Type.bool); + setAnswer(ConfigureNodeFields.notify_delete.getFieldName(), notify); + } + + /** + * Determines whether subscribers should be notified when items are deleted + * from the node. + * + * @return true if subscribers should be notified, false otherwise + */ + public boolean isNotifyRetract() { + return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.notify_retract)); + } + + /** + * Sets whether subscribers should be notified when items are deleted + * from the node. + * + * @param notify true if subscribers should be notified, false otherwise + */ + public void setNotifyRetract(boolean notify) { + addField(ConfigureNodeFields.notify_retract, FormField.Type.bool); + setAnswer(ConfigureNodeFields.notify_retract.getFieldName(), notify); + } + + /** + * Determines the type of notifications which are sent. + * + * @return NotificationType for the node configuration + * @since 4.3 + */ + public NotificationType getNotificationType() { + String value = getFieldValue(ConfigureNodeFields.notification_type); + if (value == null) + return null; + return NotificationType.valueOf(value); + } + + /** + * Sets the NotificationType for the node. + * + * @param notificationType The enum representing the possible options + * @since 4.3 + */ + public void setNotificationType(NotificationType notificationType) { + addField(ConfigureNodeFields.notification_type, FormField.Type.list_single); + setAnswer(ConfigureNodeFields.notification_type.getFieldName(), getListSingle(notificationType.toString())); + } + + /** + * Determines whether items should be persisted in the node. + * + * @return true if items are persisted + */ + public boolean isPersistItems() { + return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.persist_items)); + } + + /** + * Sets whether items should be persisted in the node. + * + * @param persist true if items should be persisted, false otherwise + */ + public void setPersistentItems(boolean persist) { + addField(ConfigureNodeFields.persist_items, FormField.Type.bool); + setAnswer(ConfigureNodeFields.persist_items.getFieldName(), persist); + } + + /** + * Determines whether to deliver notifications to available users only. + * + * @return true if users must be available + */ + public boolean isPresenceBasedDelivery() { + return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.presence_based_delivery)); + } + + /** + * Sets whether to deliver notifications to available users only. + * + * @param presenceBased true if user must be available, false otherwise + */ + public void setPresenceBasedDelivery(boolean presenceBased) { + addField(ConfigureNodeFields.presence_based_delivery, FormField.Type.bool); + setAnswer(ConfigureNodeFields.presence_based_delivery.getFieldName(), presenceBased); + } + + /** + * Gets the publishing model for the node, which determines who may publish to it. + * + * @return The publishing model + */ + public PublishModel getPublishModel() { + String value = getFieldValue(ConfigureNodeFields.publish_model); + + if (value == null) + return null; + else + return PublishModel.valueOf(value); + } + + /** + * Sets the publishing model for the node, which determines who may publish to it. + * + * @param publish The enum representing the possible options for the publishing model + */ + public void setPublishModel(PublishModel publish) { + addField(ConfigureNodeFields.publish_model, FormField.Type.list_single); + setAnswer(ConfigureNodeFields.publish_model.getFieldName(), getListSingle(publish.toString())); + } + + /** + * List of the multi user chat rooms that are specified as reply rooms. + * + * @return The reply room JID's + */ + public List getReplyRoom() { + return getFieldValues(ConfigureNodeFields.replyroom); + } + + /** + * Sets the multi user chat rooms that are specified as reply rooms. + * + * @param replyRooms The multi user chat room to use as reply rooms + */ + public void setReplyRoom(List replyRooms) { + addField(ConfigureNodeFields.replyroom, FormField.Type.list_multi); + setAnswer(ConfigureNodeFields.replyroom.getFieldName(), replyRooms); + } + + /** + * Gets the specific JID's for reply to. + * + * @return The JID's + */ + public List getReplyTo() { + return getFieldValues(ConfigureNodeFields.replyto); + } + + /** + * Sets the specific JID's for reply to. + * + * @param replyTos The JID's to reply to + */ + public void setReplyTo(List replyTos) { + addField(ConfigureNodeFields.replyto, FormField.Type.list_multi); + setAnswer(ConfigureNodeFields.replyto.getFieldName(), replyTos); + } + + /** + * Gets the roster groups that are allowed to subscribe and retrieve items. + * + * @return The roster groups + */ + public List getRosterGroupsAllowed() { + return getFieldValues(ConfigureNodeFields.roster_groups_allowed); + } + + /** + * Sets the roster groups that are allowed to subscribe and retrieve items. + * + * @param groups The roster groups + */ + public void setRosterGroupsAllowed(List groups) { + addField(ConfigureNodeFields.roster_groups_allowed, FormField.Type.list_multi); + setAnswer(ConfigureNodeFields.roster_groups_allowed.getFieldName(), groups); + } + + /** + * Determines if subscriptions are allowed. + * + * @return true if subscriptions are allowed, false otherwise + * @deprecated use {@link #isSubscribe()} instead + */ + @Deprecated + // TODO: Remove in Smack 4.5. + public boolean isSubscibe() { + return isSubscribe(); + } + + /** + * Determines if subscriptions are allowed. + * + * @return true if subscriptions are allowed, false otherwise + */ + public boolean isSubscribe() { + return ParserUtils.parseXmlBoolean(getFieldValue(ConfigureNodeFields.subscribe)); + } + + /** + * Sets whether subscriptions are allowed. + * + * @param subscribe true if they are, false otherwise + */ + public void setSubscribe(boolean subscribe) { + addField(ConfigureNodeFields.subscribe, FormField.Type.bool); + setAnswer(ConfigureNodeFields.subscribe.getFieldName(), subscribe); + } + + /** + * Gets the human readable node title. + * + * @return The node title + */ + @Override + public String getTitle() { + return getFieldValue(ConfigureNodeFields.title); + } + + /** + * Sets a human readable title for the node. + * + * @param title The node title + */ + @Override + public void setTitle(String title) { + addField(ConfigureNodeFields.title, FormField.Type.text_single); + setAnswer(ConfigureNodeFields.title.getFieldName(), title); + } + + /** + * The type of node data, usually specified by the namespace of the payload (if any). + * + * @return The type of node data + */ + public String getDataType() { + return getFieldValue(ConfigureNodeFields.type); + } + + /** + * Sets the type of node data, usually specified by the namespace of the payload (if any). + * + * @param type The type of node data + */ + public void setDataType(String type) { + addField(ConfigureNodeFields.type, FormField.Type.text_single); + setAnswer(ConfigureNodeFields.type.getFieldName(), type); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(getClass().getName() + " Content ["); + + for (FormField formField : getFields()) { + result.append('('); + result.append(formField.getVariable()); + result.append(':'); + + StringBuilder valuesBuilder = new StringBuilder(); + + for (CharSequence value : formField.getValues()) { + if (valuesBuilder.length() > 0) + result.append(','); + valuesBuilder.append(value); + } + + if (valuesBuilder.length() == 0) + valuesBuilder.append("NOT SET"); + result.append(valuesBuilder); + result.append(')'); + } + result.append(']'); + return result.toString(); + } + + private String getFieldValue(ConfigureNodeFields field) { + FormField formField = getField(field.getFieldName()); + + return formField.getFirstValue(); + } + + private List getFieldValues(ConfigureNodeFields field) { + FormField formField = getField(field.getFieldName()); + + return formField.getValuesAsString(); + } + + private void addField(ConfigureNodeFields nodeField, FormField.Type type) { + String fieldName = nodeField.getFieldName(); + + if (getField(fieldName) == null) { + FormField field = FormField.builder() + .setVariable(fieldName) + .setType(type) + .build(); + addField(field); + } + } + + private static List getListSingle(String value) { + List list = new ArrayList<>(1); + list.add(value); + return list; + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java index 7ddf24468..c828f061b 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java @@ -18,13 +18,12 @@ package org.jivesoftware.smackx.pubsub; import java.net.URL; -import org.jivesoftware.smackx.pubsub.form.ConfigureForm; -import org.jivesoftware.smackx.xdata.form.FilledForm; +import org.jivesoftware.smackx.xdata.Form; /** * This enumeration represents all the fields of a node configuration form. This enumeration * is not required when using the {@link ConfigureForm} to configure nodes, but may be helpful - * for generic UI's using only a {@link FilledForm} for configuration. + * for generic UI's using only a {@link Form} for configuration. * * @author Robin Collier */ @@ -177,6 +176,20 @@ public enum ConfigureNodeFields { */ publish_model, + /** + * The specific multi-user chat rooms to specify for replyroom. + * + *

Value: List of JIDs as Strings

+ */ + replyroom, + + /** + * The specific JID(s) to specify for replyto. + * + *

Value: List of JIDs as Strings

+ */ + replyto, + /** * The roster group(s) allowed to subscribe and retrieve items. * diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java index 6c3c69171..9562dd4a0 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java @@ -16,7 +16,7 @@ */ package org.jivesoftware.smackx.pubsub; -import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jivesoftware.smackx.xdata.Form; /** * Generic stanza extension which represents any PubSub form that is @@ -27,7 +27,7 @@ import org.jivesoftware.smackx.xdata.packet.DataForm; * @author Robin Collier */ public class FormNode extends NodeExtension { - private final DataForm configForm; + private final Form configForm; /** * Create a {@link FormNode} which contains the specified form. @@ -35,7 +35,7 @@ public class FormNode extends NodeExtension { * @param formType The type of form being sent * @param submitForm The form */ - public FormNode(FormNodeType formType, DataForm submitForm) { + public FormNode(FormNodeType formType, Form submitForm) { super(formType.getNodeElement()); if (submitForm == null) @@ -51,7 +51,7 @@ public class FormNode extends NodeExtension { * @param nodeId The node the form is associated with * @param submitForm The form */ - public FormNode(FormNodeType formType, String nodeId, DataForm submitForm) { + public FormNode(FormNodeType formType, String nodeId, Form submitForm) { super(formType.getNodeElement(), nodeId); if (submitForm == null) @@ -64,7 +64,7 @@ public class FormNode extends NodeExtension { * * @return The form */ - public DataForm getForm() { + public Form getForm() { return configForm; } @@ -84,7 +84,7 @@ public class FormNode extends NodeExtension { } else builder.append('>'); - builder.append(configForm.toXML()); + builder.append(configForm.getDataFormToSend().toXML()); builder.append("'); return builder.toString(); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Item.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Item.java index 727de6c48..eaee0b168 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Item.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Item.java @@ -19,7 +19,6 @@ package org.jivesoftware.smackx.pubsub; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.util.XmlStringBuilder; -import org.jivesoftware.smackx.pubsub.form.ConfigureForm; import org.jivesoftware.smackx.pubsub.provider.ItemProvider; /** diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemReply.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemReply.java index eb93ec2d9..67051349d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemReply.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemReply.java @@ -16,10 +16,8 @@ */ package org.jivesoftware.smackx.pubsub; -import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm; - /** - * These are the options for the node configuration setting {@link FillableConfigureForm#setItemReply(ItemReply)}, + * These are the options for the node configuration setting {@link ConfigureForm#setItemReply(ItemReply)}, * which defines who should receive replies to items. * * @author Robin Collier diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemsExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemsExtension.java index 000138897..12d7a4aa6 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemsExtension.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ItemsExtension.java @@ -135,7 +135,6 @@ public class ItemsExtension extends NodeExtension implements EmbeddedPacketExten * * @return List of {@link Item}, {@link RetractItem}, or null */ - // TODO: Shouldn't this return List? Why is RetractItem not a subtype of item? public List getItems() { return items; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/LeafNode.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/LeafNode.java index e604a47d4..a6eb39650 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/LeafNode.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/LeafNode.java @@ -27,7 +27,6 @@ import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.IQ.Type; import org.jivesoftware.smackx.disco.packet.DiscoverItems; -import org.jivesoftware.smackx.pubsub.form.ConfigureForm; import org.jivesoftware.smackx.pubsub.packet.PubSub; /** diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Node.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Node.java index 313d0b93c..88decd520 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Node.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/Node.java @@ -37,10 +37,6 @@ import org.jivesoftware.smackx.delay.DelayInformationManager; import org.jivesoftware.smackx.disco.packet.DiscoverInfo; import org.jivesoftware.smackx.pubsub.Affiliation.AffiliationNamespace; import org.jivesoftware.smackx.pubsub.SubscriptionsExtension.SubscriptionsNamespace; -import org.jivesoftware.smackx.pubsub.form.ConfigureForm; -import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm; -import org.jivesoftware.smackx.pubsub.form.FillableSubscribeForm; -import org.jivesoftware.smackx.pubsub.form.SubscribeForm; import org.jivesoftware.smackx.pubsub.listener.ItemDeleteListener; import org.jivesoftware.smackx.pubsub.listener.ItemEventListener; import org.jivesoftware.smackx.pubsub.listener.NodeConfigListener; @@ -49,7 +45,7 @@ import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; import org.jivesoftware.smackx.pubsub.util.NodeUtils; import org.jivesoftware.smackx.shim.packet.Header; import org.jivesoftware.smackx.shim.packet.HeadersExtension; -import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jivesoftware.smackx.xdata.Form; import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; @@ -85,7 +81,7 @@ public abstract class Node { } /** * Returns a configuration form, from which you can create an answer form to be submitted - * via the {@link #sendConfigurationForm(FillableConfigureForm)}. + * via the {@link #sendConfigurationForm(Form)}. * * @return the configuration form * @throws XMPPErrorException if there was an XMPP error returned. @@ -101,17 +97,17 @@ public abstract class Node { } /** - * Update the configuration with the contents of the new {@link FillableConfigureForm}. + * Update the configuration with the contents of the new {@link Form}. * - * @param configureForm the filled node configuration form with the nodes new configuration. + * @param submitForm TODO javadoc me please * @throws XMPPErrorException if there was an XMPP error returned. * @throws NoResponseException if there was no response from the remote entity. * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public void sendConfigurationForm(FillableConfigureForm configureForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public void sendConfigurationForm(Form submitForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { PubSub packet = createPubsubPacket(Type.set, new FormNode(FormNodeType.CONFIGURE_OWNER, - getId(), configureForm.getDataFormToSubmit())); + getId(), submitForm)); pubSubManager.getConnection().createStanzaCollectorAndSend(packet).nextResultOrThrow(); } @@ -458,10 +454,9 @@ public abstract class Node { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public Subscription subscribe(Jid jid, FillableSubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - DataForm submitForm = subForm.getDataFormToSubmit(); + public Subscription subscribe(Jid jid, SubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { PubSub request = createPubsubPacket(Type.set, new SubscribeExtension(jid, getId())); - request.addExtension(new FormNode(FormNodeType.OPTIONS, submitForm)); + request.addExtension(new FormNode(FormNodeType.OPTIONS, subForm)); PubSub reply = sendPubsubPacket(request); return reply.getExtension(PubSubElementType.SUBSCRIPTION); } @@ -488,11 +483,11 @@ public abstract class Node { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. * @throws IllegalArgumentException if the provided string is not a valid JID. - * @deprecated use {@link #subscribe(Jid, FillableSubscribeForm)} instead. + * @deprecated use {@link #subscribe(Jid, SubscribeForm)} instead. */ @Deprecated // TODO: Remove in Smack 4.5. - public Subscription subscribe(String jidString, FillableSubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public Subscription subscribe(String jidString, SubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { Jid jid; try { jid = JidCreate.from(jidString); @@ -534,7 +529,7 @@ public abstract class Node { /** * Returns a SubscribeForm for subscriptions, from which you can create an answer form to be submitted - * via the {@link #sendConfigurationForm(FillableConfigureForm)}. + * via the {@link #sendConfigurationForm(Form)}. * * @param jid TODO javadoc me please * diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/NotificationType.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/NotificationType.java index d915b2bc6..812f56b21 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/NotificationType.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/NotificationType.java @@ -16,11 +16,9 @@ */ package org.jivesoftware.smackx.pubsub; -import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm; - /** * Specify the delivery style for event notifications. Denotes possible values - * for {@link FillableConfigureForm#setNotificationType(NotificationType)}. + * for {@link ConfigureForm#setNotificationType(NotificationType)}. * * @author Timothy Pitt */ diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PayloadItem.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PayloadItem.java index f22a8e876..f2c9058aa 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PayloadItem.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PayloadItem.java @@ -20,7 +20,6 @@ import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.util.XmlStringBuilder; -import org.jivesoftware.smackx.pubsub.form.ConfigureForm; import org.jivesoftware.smackx.pubsub.provider.ItemProvider; /** diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PresenceState.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PresenceState.java index 7fbe9e094..e4fee0d65 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PresenceState.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PresenceState.java @@ -16,8 +16,6 @@ */ package org.jivesoftware.smackx.pubsub; -import org.jivesoftware.smackx.pubsub.form.SubscribeForm; - /** * Defines the possible valid presence states for node subscription via * {@link SubscribeForm#getShowValues()}. diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubManager.java index f328bde81..7d91e41ca 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PubSubManager.java @@ -46,12 +46,11 @@ import org.jivesoftware.smackx.disco.packet.DiscoverInfo; import org.jivesoftware.smackx.disco.packet.DiscoverItems; import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException; import org.jivesoftware.smackx.pubsub.PubSubException.NotAPubSubNodeException; -import org.jivesoftware.smackx.pubsub.form.ConfigureForm; -import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm; import org.jivesoftware.smackx.pubsub.packet.PubSub; import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; import org.jivesoftware.smackx.pubsub.util.NodeUtils; -import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jivesoftware.smackx.xdata.Form; +import org.jivesoftware.smackx.xdata.FormField; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.DomainBareJid; @@ -70,8 +69,6 @@ import org.jxmpp.stringprep.XmppStringprepException; */ public final class PubSubManager extends Manager { - public static final String PLUS_NOTIFY = "+notify"; - public static final String AUTO_CREATE_FEATURE = "http://jabber.org/protocol/pubsub#auto-create"; private static final Logger LOGGER = Logger.getLogger(PubSubManager.class.getName()); @@ -256,18 +253,16 @@ public final class PubSubManager extends Manager { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public Node createNode(String nodeId, FillableConfigureForm config) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public Node createNode(String nodeId, Form config) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { PubSub request = PubSub.createPubsubPacket(pubSubService, Type.set, new NodeExtension(PubSubElementType.CREATE, nodeId)); boolean isLeafNode = true; if (config != null) { - DataForm submitForm = config.getDataFormToSubmit(); - request.addExtension(new FormNode(FormNodeType.CONFIGURE, submitForm)); - NodeType nodeType = config.getNodeType(); - // Note that some implementations do to have the pubsub#node_type field in their defauilt configuration, - // which I believe to be a bug. However, since PubSub specifies the default node type to be 'leaf' we assume - // leaf if the field does not exist. - isLeafNode = nodeType == null || nodeType == NodeType.leaf; + request.addExtension(new FormNode(FormNodeType.CONFIGURE, config)); + FormField nodeTypeField = config.getField(ConfigureNodeFields.node_type.getFieldName()); + + if (nodeTypeField != null) + isLeafNode = nodeTypeField.getValues().get(0).toString().equals(NodeType.leaf.toString()); } // Errors will cause exceptions in getReply, so it only returns diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PublishModel.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PublishModel.java index 321866cbc..f92ff24b3 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PublishModel.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/PublishModel.java @@ -16,11 +16,9 @@ */ package org.jivesoftware.smackx.pubsub; -import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm; - /** * Determines who may publish to a node. Denotes possible values - * for {@link FillableConfigureForm#setPublishModel(PublishModel)}. + * for {@link ConfigureForm#setPublishModel(PublishModel)}. * * @author Robin Collier */ diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java new file mode 100644 index 000000000..cdcaffaf5 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java @@ -0,0 +1,214 @@ +/** + * + * Copyright the original author or authors + * + * 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.smackx.pubsub; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.UnknownFormatConversionException; + +import org.jivesoftware.smack.util.ParserUtils; + +import org.jivesoftware.smackx.xdata.Form; +import org.jivesoftware.smackx.xdata.FormField; +import org.jivesoftware.smackx.xdata.packet.DataForm; + +import org.jxmpp.util.XmppDateTime; + +/** + * A decorator for a {@link Form} to easily enable reading and updating + * of subscription options. All operations read or update the underlying {@link DataForm}. + * + *

Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not + * exist, all SubscribeForm.setXXX methods will create the field in the wrapped form + * if it does not already exist. + * + * @author Robin Collier + */ +public class SubscribeForm extends Form { + public SubscribeForm(DataForm configDataForm) { + super(configDataForm); + } + + public SubscribeForm(Form subscribeOptionsForm) { + super(subscribeOptionsForm.getDataFormToSend()); + } + + public SubscribeForm(DataForm.Type formType) { + super(formType); + } + + /** + * Determines if an entity wants to receive notifications. + * + * @return true if want to receive, false otherwise + */ + public boolean isDeliverOn() { + return ParserUtils.parseXmlBoolean(getFieldValue(SubscribeOptionFields.deliver)); + } + + /** + * Sets whether an entity wants to receive notifications. + * + * @param deliverNotifications TODO javadoc me please + */ + public void setDeliverOn(boolean deliverNotifications) { + addField(SubscribeOptionFields.deliver, FormField.Type.bool); + setAnswer(SubscribeOptionFields.deliver.getFieldName(), deliverNotifications); + } + + /** + * Determines if notifications should be delivered as aggregations or not. + * + * @return true to aggregate, false otherwise + */ + public boolean isDigestOn() { + return ParserUtils.parseXmlBoolean(getFieldValue(SubscribeOptionFields.digest)); + } + + /** + * Sets whether notifications should be delivered as aggregations or not. + * + * @param digestOn true to aggregate, false otherwise + */ + public void setDigestOn(boolean digestOn) { + addField(SubscribeOptionFields.deliver, FormField.Type.bool); + setAnswer(SubscribeOptionFields.deliver.getFieldName(), digestOn); + } + + /** + * Gets the minimum number of milliseconds between sending notification digests. + * + * @return The frequency in milliseconds + */ + public int getDigestFrequency() { + return Integer.parseInt(getFieldValue(SubscribeOptionFields.digest_frequency)); + } + + /** + * Sets the minimum number of milliseconds between sending notification digests. + * + * @param frequency The frequency in milliseconds + */ + public void setDigestFrequency(int frequency) { + addField(SubscribeOptionFields.digest_frequency, FormField.Type.text_single); + setAnswer(SubscribeOptionFields.digest_frequency.getFieldName(), frequency); + } + + /** + * Get the time at which the leased subscription will expire, or has expired. + * + * @return The expiry date + */ + public Date getExpiry() { + String dateTime = getFieldValue(SubscribeOptionFields.expire); + try { + return XmppDateTime.parseDate(dateTime); + } + catch (ParseException e) { + UnknownFormatConversionException exc = new UnknownFormatConversionException(dateTime); + exc.initCause(e); + throw exc; + } + } + + /** + * Sets the time at which the leased subscription will expire, or has expired. + * + * @param expire The expiry date + */ + public void setExpiry(Date expire) { + addField(SubscribeOptionFields.expire, FormField.Type.text_single); + setAnswer(SubscribeOptionFields.expire.getFieldName(), XmppDateTime.formatXEP0082Date(expire)); + } + + /** + * Determines whether the entity wants to receive an XMPP message body in + * addition to the payload format. + * + * @return true to receive the message body, false otherwise + */ + public boolean isIncludeBody() { + return ParserUtils.parseXmlBoolean(getFieldValue(SubscribeOptionFields.include_body)); + } + + /** + * Sets whether the entity wants to receive an XMPP message body in + * addition to the payload format. + * + * @param include true to receive the message body, false otherwise + */ + public void setIncludeBody(boolean include) { + addField(SubscribeOptionFields.include_body, FormField.Type.bool); + setAnswer(SubscribeOptionFields.include_body.getFieldName(), include); + } + + /** + * Gets the {@link PresenceState} for which an entity wants to receive + * notifications. + * + * @return the list of states + */ + public List getShowValues() { + ArrayList result = new ArrayList<>(5); + + for (String state : getFieldValues(SubscribeOptionFields.show_values)) { + result.add(PresenceState.valueOf(state)); + } + return result; + } + + /** + * Sets the list of {@link PresenceState} for which an entity wants + * to receive notifications. + * + * @param stateValues The list of states + */ + public void setShowValues(Collection stateValues) { + ArrayList values = new ArrayList<>(stateValues.size()); + + for (PresenceState state : stateValues) { + values.add(state.toString()); + } + addField(SubscribeOptionFields.show_values, FormField.Type.list_multi); + setAnswer(SubscribeOptionFields.show_values.getFieldName(), values); + } + + private String getFieldValue(SubscribeOptionFields field) { + FormField formField = getField(field.getFieldName()); + + return formField.getFirstValue(); + } + + private List getFieldValues(SubscribeOptionFields field) { + FormField formField = getField(field.getFieldName()); + + return formField.getValuesAsString(); + } + + private void addField(SubscribeOptionFields nodeField, FormField.Type type) { + String fieldName = nodeField.getFieldName(); + + if (getField(fieldName) == null) { + FormField.Builder field = FormField.builder(fieldName); + field.setType(type); + addField(field.build()); + } + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/filter/EventItemsExtensionFilter.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/filter/EventItemsExtensionFilter.java deleted file mode 100644 index 03f87cd46..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/filter/EventItemsExtensionFilter.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * - * 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.smackx.pubsub.filter; - -import org.jivesoftware.smack.filter.ExtensionElementFilter; - -import org.jivesoftware.smackx.pubsub.EventElement; -import org.jivesoftware.smackx.pubsub.EventElementType; - -public final class EventItemsExtensionFilter extends ExtensionElementFilter { - - public static final EventItemsExtensionFilter INSTANCE = new EventItemsExtensionFilter(); - - private EventItemsExtensionFilter() { - super(EventElement.class); - } - - @Override - public boolean accept(EventElement eventElement) { - EventElementType eventElementType = eventElement.getEventType(); - return eventElementType == EventElementType.items; - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/ConfigureForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/ConfigureForm.java deleted file mode 100644 index f6a4c78b3..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/ConfigureForm.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * - * 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.smackx.pubsub.form; - -import org.jivesoftware.smackx.xdata.form.Form; -import org.jivesoftware.smackx.xdata.packet.DataForm; - -public class ConfigureForm extends Form implements ConfigureFormReader { - - public ConfigureForm(DataForm dataForm) { - super(dataForm); - ensureFormType(dataForm, FORM_TYPE); - } - - @Override - public FillableConfigureForm getFillableForm() { - return new FillableConfigureForm(getDataForm()); - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/ConfigureFormReader.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/ConfigureFormReader.java deleted file mode 100644 index 0184c41d8..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/ConfigureFormReader.java +++ /dev/null @@ -1,292 +0,0 @@ -/** - * - * Copyright the original author or authors, 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.smackx.pubsub.form; - -import java.util.Collections; -import java.util.List; - -import org.jivesoftware.smackx.pubsub.AccessModel; -import org.jivesoftware.smackx.pubsub.ChildrenAssociationPolicy; -import org.jivesoftware.smackx.pubsub.ConfigureNodeFields; -import org.jivesoftware.smackx.pubsub.ItemReply; -import org.jivesoftware.smackx.pubsub.NodeType; -import org.jivesoftware.smackx.pubsub.NotificationType; -import org.jivesoftware.smackx.pubsub.PublishModel; -import org.jivesoftware.smackx.pubsub.packet.PubSub; -import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.JidMultiFormField; -import org.jivesoftware.smackx.xdata.form.FormReader; - -import org.jxmpp.jid.Jid; - -public interface ConfigureFormReader extends FormReader { - - String FORM_TYPE = PubSub.NAMESPACE + "#node_config"; - - /** - * Get the currently configured {@link AccessModel}, null if it is not set. - * - * @return The current {@link AccessModel} - */ - default AccessModel getAccessModel() { - String value = readFirstValue(ConfigureNodeFields.access_model.getFieldName()); - if (value == null) { - return null; - } - return AccessModel.valueOf(value); - } - - /** - * Returns the URL of an XSL transformation which can be applied to payloads in order to - * generate an appropriate message body element. - * - * @return URL to an XSL - */ - default String getBodyXSLT() { - return readFirstValue(ConfigureNodeFields.body_xslt.getFieldName()); - } - - /** - * The id's of the child nodes associated with a collection node (both leaf and collection). - * - * @return list of child nodes. - */ - default List getChildren() { - return readStringValues(ConfigureNodeFields.children.getFieldName()); - } - - /** - * Returns the policy that determines who may associate children with the node. - * - * @return The current policy - */ - default ChildrenAssociationPolicy getChildrenAssociationPolicy() { - String value = readFirstValue(ConfigureNodeFields.children_association_policy.getFieldName()); - if (value == null) { - return null; - } - return ChildrenAssociationPolicy.valueOf(value); - } - - /** - * List of JID's that are on the whitelist that determines who can associate child nodes - * with the collection node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to - * {@link ChildrenAssociationPolicy#whitelist}. - * - * @return List of the whitelist - */ - default List getChildrenAssociationWhitelist() { - FormField formField = read(ConfigureNodeFields.children_association_whitelist.getFieldName()); - if (formField == null) { - Collections.emptyList(); - } - JidMultiFormField jidMultiFormField = formField.ifPossibleAs(JidMultiFormField.class); - return jidMultiFormField.getValues(); - } - - /** - * Gets the maximum number of child nodes that can be associated with the collection node. - * - * @return The maximum number of child nodes - */ - default Integer getChildrenMax() { - return readInteger(ConfigureNodeFields.children_max.getFieldName()); - } - - /** - * Gets the collection node which the node is affiliated with. - * - * @return The collection node id - */ - default List getCollection() { - return readValues(ConfigureNodeFields.collection.getFieldName()); - } - - /** - * Gets the URL of an XSL transformation which can be applied to the payload - * format in order to generate a valid Data Forms result that the client could - * display using a generic Data Forms rendering engine. - * - * @return The URL of an XSL transformation - */ - default String getDataformXSLT() { - return readFirstValue(ConfigureNodeFields.dataform_xslt.getFieldName()); - } - - /** - * Does the node deliver payloads with event notifications. - * - * @return true if it does, false otherwise - */ - default Boolean isDeliverPayloads() { - return readBoolean(ConfigureNodeFields.deliver_payloads.getFieldName()); - } - - /** - * Determines who should get replies to items. - * - * @return Who should get the reply - */ - default ItemReply getItemReply() { - String value = readFirstValue(ConfigureNodeFields.itemreply.getFieldName()); - if (value == null) { - return null; - } - return ItemReply.valueOf(value); - } - - /** - * Gets the maximum number of items to persisted to this node if {@link #isPersistItems()} is - * true. - * - * @return The maximum number of items to persist - */ - default Integer getMaxItems() { - return readInteger(ConfigureNodeFields.max_items.getFieldName()); - } - - /** - * Gets the maximum payload size in bytes. - * - * @return The maximum payload size - */ - default Integer getMaxPayloadSize() { - return readInteger(ConfigureNodeFields.max_payload_size.getFieldName()); - } - - /** - * Gets the node type. - * - * @return The node type - */ - default NodeType getNodeType() { - String value = readFirstValue(ConfigureNodeFields.node_type.getFieldName()); - if (value == null) { - return null; - } - return NodeType.valueOf(value); - } - - /** - * Determines if subscribers should be notified when the configuration changes. - * - * @return true if they should be notified, false otherwise - */ - default Boolean isNotifyConfig() { - return readBoolean(ConfigureNodeFields.notify_config.getFieldName()); - } - - /** - * Determines whether subscribers should be notified when the node is deleted. - * - * @return true if subscribers should be notified, false otherwise - */ - default Boolean isNotifyDelete() { - return readBoolean(ConfigureNodeFields.notify_delete.getFieldName()); - } - - /** - * Determines whether subscribers should be notified when items are deleted - * from the node. - * - * @return true if subscribers should be notified, false otherwise - */ - default Boolean isNotifyRetract() { - return readBoolean(ConfigureNodeFields.notify_retract.getFieldName()); - } - - /** - * Determines the type of notifications which are sent. - * - * @return NotificationType for the node configuration - * @since 4.3 - */ - default NotificationType getNotificationType() { - String value = readFirstValue(ConfigureNodeFields.notification_type.getFieldName()); - if (value == null) { - return null; - } - return NotificationType.valueOf(value); - } - - /** - * Determines whether items should be persisted in the node. - * - * @return true if items are persisted - */ - default boolean isPersistItems() { - return readBoolean(ConfigureNodeFields.persist_items.getFieldName()); - } - - /** - * Determines whether to deliver notifications to available users only. - * - * @return true if users must be available - */ - default boolean isPresenceBasedDelivery() { - return readBoolean(ConfigureNodeFields.presence_based_delivery.getFieldName()); - } - - /** - * Gets the publishing model for the node, which determines who may publish to it. - * - * @return The publishing model - */ - default PublishModel getPublishModel() { - String value = readFirstValue(ConfigureNodeFields.publish_model.getFieldName()); - if (value == null) { - return null; - } - return PublishModel.valueOf(value); - } - - /** - * Gets the roster groups that are allowed to subscribe and retrieve items. - * - * @return The roster groups - */ - default List getRosterGroupsAllowed() { - return readStringValues(ConfigureNodeFields.roster_groups_allowed.getFieldName()); - } - - /** - * Determines if subscriptions are allowed. - * - * @return true if subscriptions are allowed, false otherwise - */ - default boolean isSubscribe() { - return readBoolean(ConfigureNodeFields.subscribe.getFieldName()); - } - - /** - * Gets the human readable node title. - * - * @return The node title - */ - default String getTitle() { - return readFirstValue(ConfigureNodeFields.title.getFieldName()); - } - - /** - * The type of node data, usually specified by the namespace of the payload (if any). - * - * @return The type of node data - */ - default String getDataType() { - return readFirstValue(ConfigureNodeFields.type.getFieldName()); - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FillableConfigureForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FillableConfigureForm.java deleted file mode 100644 index 0d7ebd039..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FillableConfigureForm.java +++ /dev/null @@ -1,314 +0,0 @@ -/** - * - * Copyright the original author or authors, 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.smackx.pubsub.form; - -import java.net.URL; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.jivesoftware.smackx.pubsub.AccessModel; -import org.jivesoftware.smackx.pubsub.ChildrenAssociationPolicy; -import org.jivesoftware.smackx.pubsub.ConfigureNodeFields; -import org.jivesoftware.smackx.pubsub.ItemReply; -import org.jivesoftware.smackx.pubsub.NodeType; -import org.jivesoftware.smackx.pubsub.NotificationType; -import org.jivesoftware.smackx.pubsub.PublishModel; -import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.form.FillableForm; -import org.jivesoftware.smackx.xdata.packet.DataForm; - -import org.jxmpp.jid.Jid; - -public class FillableConfigureForm extends FillableForm implements ConfigureFormReader { - - public FillableConfigureForm(DataForm dataForm) { - super(dataForm); - } - - /** - * Sets the value of access model. - * - * @param accessModel TODO javadoc me please - */ - public void setAccessModel(AccessModel accessModel) { - FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.access_model.getFieldName()) - .setValue(accessModel) - .build(); - write(formField); - } - - /** - * Set the URL of an XSL transformation which can be applied to payloads in order to - * generate an appropriate message body element. - * - * @param bodyXslt The URL of an XSL - */ - public void setBodyXSLT(String bodyXslt) { - FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.body_xslt.getFieldName()) - .setValue(bodyXslt) - .build(); - write(formField); - } - - /** - * Set the list of child node ids that are associated with a collection node. - * - * @param children TODO javadoc me please - */ - public void setChildren(List children) { - FormField formField = FormField.textMultiBuilder(ConfigureNodeFields.children.getFieldName()) - .addValues(children) - .build(); - write(formField); - } - - /** - * Sets the policy that determines who may associate children with the node. - * - * @param policy The policy being set - */ - public void setChildrenAssociationPolicy(ChildrenAssociationPolicy policy) { - FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.children_association_policy.getFieldName()) - .setValue(policy) - .build(); - write(formField); - } - - /** - * Set the JID's in the whitelist of users that can associate child nodes with the collection - * node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to - * {@link ChildrenAssociationPolicy#whitelist}. - * - * @param whitelist The list of JID's - */ - public void setChildrenAssociationWhitelist(List whitelist) { - FormField formField = FormField.jidMultiBuilder(ConfigureNodeFields.children_association_whitelist.getFieldName()) - .addValues(whitelist) - .build(); - write(formField); - } - - /** - * Set the maximum number of child nodes that can be associated with a collection node. - * - * @param max The maximum number of child nodes. - */ - public void setChildrenMax(int max) { - FormField formField = FormField.textSingleBuilder(ConfigureNodeFields.children_max.getFieldName()) - .setValue(max) - .build(); - write(formField); - } - - /** - * Sets the collection node which the node is affiliated with. - * - * @param collection The node id of the collection node - */ - public void setCollection(String collection) { - setCollections(Collections.singletonList(collection)); - } - - public void setCollections(Collection collections) { - FormField formField = FormField.textMultiBuilder(ConfigureNodeFields.collection.getFieldName()) - .addValues(collections) - .build(); - write(formField); - } - - /** - * Sets the URL of an XSL transformation which can be applied to the payload - * format in order to generate a valid Data Forms result that the client could - * display using a generic Data Forms rendering engine. - * - * @param url The URL of an XSL transformation - */ - public void setDataformXSLT(URL url) { - FormField formField = FormField.textSingleBuilder(ConfigureNodeFields.dataform_xslt.getFieldName()) - .setValue(url) - .build(); - write(formField); - } - - /** - * Sets whether the node will deliver payloads with event notifications. - * - * @param deliver true if the payload will be delivered, false otherwise - */ - public void setDeliverPayloads(boolean deliver) { - writeBoolean(ConfigureNodeFields.deliver_payloads.getFieldName(), deliver); - } - - /** - * Sets who should get the replies to items. - * - * @param reply Defines who should get the reply - */ - public void setItemReply(ItemReply reply) { - FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.itemreply.getFieldName()) - .setValue(reply) - .build(); - write(formField); - } - - /** - * Set the maximum number of items to persisted to this node if {@link #isPersistItems()} is - * true. - * - * @param max The maximum number of items to persist - */ - public void setMaxItems(int max) { - FormField formField = FormField.textSingleBuilder(ConfigureNodeFields.max_items.getFieldName()) - .setValue(max) - .build(); - write(formField); - } - - /** - * Sets the maximum payload size in bytes. - * - * @param max The maximum payload size - */ - public void setMaxPayloadSize(int max) { - FormField formField = FormField.textSingleBuilder(ConfigureNodeFields.max_payload_size.getFieldName()) - .setValue(max) - .build(); - write(formField); - } - - /** - * Sets the node type. - * - * @param type The node type - */ - public void setNodeType(NodeType type) { - FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.node_type.getFieldName()) - .setValue(type) - .build(); - write(formField); - } - - /** - * Sets whether subscribers should be notified when the configuration changes. - * - * @param notify true if subscribers should be notified, false otherwise - */ - public void setNotifyConfig(boolean notify) { - writeBoolean(ConfigureNodeFields.notify_config.getFieldName(), notify); - } - - /** - * Sets whether subscribers should be notified when the node is deleted. - * - * @param notify true if subscribers should be notified, false otherwise - */ - public void setNotifyDelete(boolean notify) { - writeBoolean(ConfigureNodeFields.notify_delete.getFieldName(), notify); - } - - - /** - * Sets whether subscribers should be notified when items are deleted - * from the node. - * - * @param notify true if subscribers should be notified, false otherwise - */ - public void setNotifyRetract(boolean notify) { - writeBoolean(ConfigureNodeFields.notify_retract.getFieldName(), notify); - } - - /** - * Sets the NotificationType for the node. - * - * @param notificationType The enum representing the possible options - * @since 4.3 - */ - public void setNotificationType(NotificationType notificationType) { - FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.notification_type.getFieldName()) - .setValue(notificationType) - .build(); - write(formField); - } - - /** - * Sets whether items should be persisted in the node. - * - * @param persist true if items should be persisted, false otherwise - */ - public void setPersistentItems(boolean persist) { - writeBoolean(ConfigureNodeFields.persist_items.getFieldName(), persist); - } - - /** - * Sets whether to deliver notifications to available users only. - * - * @param presenceBased true if user must be available, false otherwise - */ - public void setPresenceBasedDelivery(boolean presenceBased) { - writeBoolean(ConfigureNodeFields.presence_based_delivery.getFieldName(), presenceBased); - } - - - /** - * Sets the publishing model for the node, which determines who may publish to it. - * - * @param publish The enum representing the possible options for the publishing model - */ - public void setPublishModel(PublishModel publish) { - FormField formField = FormField.listSingleBuilder(ConfigureNodeFields.publish_model.getFieldName()) - .setValue(publish) - .build(); - write(formField); - } - - /** - * Sets the roster groups that are allowed to subscribe and retrieve items. - * - * @param groups The roster groups - */ - public void setRosterGroupsAllowed(List groups) { - writeListMulti(ConfigureNodeFields.roster_groups_allowed.getFieldName(), groups); - } - - /** - * Sets whether subscriptions are allowed. - * - * @param subscribe true if they are, false otherwise - */ - public void setSubscribe(boolean subscribe) { - writeBoolean(ConfigureNodeFields.subscribe.getFieldName(), subscribe); - } - - /** - * Sets a human readable title for the node. - * - * @param title The node title - */ - public void setTitle(CharSequence title) { - writeTextSingle(ConfigureNodeFields.title.getFieldName(), title); - } - - /** - * Sets the type of node data, usually specified by the namespace of the payload (if any). - * - * @param type The type of node data - */ - public void setDataType(CharSequence type) { - writeTextSingle(ConfigureNodeFields.type.getFieldName(), type); - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FillableSubscribeForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FillableSubscribeForm.java deleted file mode 100644 index 396678097..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FillableSubscribeForm.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * - * 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.smackx.pubsub.form; - -import java.util.Collection; -import java.util.Date; - -import org.jivesoftware.smackx.pubsub.PresenceState; -import org.jivesoftware.smackx.pubsub.SubscribeOptionFields; -import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.ListMultiFormField; -import org.jivesoftware.smackx.xdata.form.FillableForm; -import org.jivesoftware.smackx.xdata.packet.DataForm; - -public class FillableSubscribeForm extends FillableForm { - - public FillableSubscribeForm(DataForm dataForm) { - super(dataForm); - } - - /** - * Sets whether an entity wants to receive notifications. - * - * @param deliverNotifications TODO javadoc me please - */ - public void setDeliverOn(boolean deliverNotifications) { - writeBoolean(SubscribeOptionFields.deliver.getFieldName(), deliverNotifications); - } - - /** - * Sets whether notifications should be delivered as aggregations or not. - * - * @param digestOn true to aggregate, false otherwise - */ - public void setDigestOn(boolean digestOn) { - writeBoolean(SubscribeOptionFields.digest.getFieldName(), digestOn); - } - - /** - * Sets the minimum number of milliseconds between sending notification digests. - * - * @param frequency The frequency in milliseconds - */ - public void setDigestFrequency(int frequency) { - write(SubscribeOptionFields.digest_frequency.getFieldName(), frequency); - } - - /** - * Sets the time at which the leased subscription will expire, or has expired. - * - * @param expire The expiry date - */ - public void setExpiry(Date expire) { - write(SubscribeOptionFields.expire.getFieldName(), expire); - } - - /** - * Sets whether the entity wants to receive an XMPP message body in - * addition to the payload format. - * - * @param include true to receive the message body, false otherwise - */ - public void setIncludeBody(boolean include) { - writeBoolean(SubscribeOptionFields.include_body.getFieldName(), include); - } - - /** - * Sets the list of {@link PresenceState} for which an entity wants - * to receive notifications. - * - * @param stateValues The list of states - */ - public void setShowValues(Collection stateValues) { - ListMultiFormField.Builder builder = FormField.listMultiBuilder(SubscribeOptionFields.show_values.getFieldName()); - for (PresenceState state : stateValues) { - builder.addValue(state.toString()); - } - - write(builder.build()); - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FilledConfigureForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FilledConfigureForm.java deleted file mode 100644 index 74d2e48f1..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FilledConfigureForm.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * - * 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.smackx.pubsub.form; - -import org.jivesoftware.smackx.xdata.form.FilledForm; -import org.jivesoftware.smackx.xdata.packet.DataForm; - -public class FilledConfigureForm extends FilledForm implements ConfigureFormReader { - - public FilledConfigureForm(DataForm dataForm) { - super(dataForm); - ensureFormType(dataForm, FORM_TYPE); - } - -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FilledSubscribeForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FilledSubscribeForm.java deleted file mode 100644 index 67a82ad3a..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FilledSubscribeForm.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * - * 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.smackx.pubsub.form; - -import org.jivesoftware.smackx.xdata.form.FilledForm; -import org.jivesoftware.smackx.xdata.packet.DataForm; - -public class FilledSubscribeForm extends FilledForm implements SubscribeFormReader { - - public FilledSubscribeForm(DataForm dataForm) { - super(dataForm); - ensureFormType(dataForm, FORM_TYPE); - } - -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/SubscribeForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/SubscribeForm.java deleted file mode 100644 index b1e173d5d..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/SubscribeForm.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * - * 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.smackx.pubsub.form; - -import org.jivesoftware.smackx.xdata.form.Form; -import org.jivesoftware.smackx.xdata.packet.DataForm; - -public class SubscribeForm extends Form implements SubscribeFormReader { - - public SubscribeForm(DataForm dataForm) { - super(dataForm); - ensureFormType(dataForm, FORM_TYPE); - } - -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/SubscribeFormReader.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/SubscribeFormReader.java deleted file mode 100644 index f6a691e58..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/SubscribeFormReader.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * - * 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.smackx.pubsub.form; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import org.jivesoftware.smackx.pubsub.PresenceState; -import org.jivesoftware.smackx.pubsub.SubscribeOptionFields; -import org.jivesoftware.smackx.pubsub.packet.PubSub; -import org.jivesoftware.smackx.xdata.form.FormReader; - -public interface SubscribeFormReader extends FormReader { - - String FORM_TYPE = PubSub.NAMESPACE + "#subscribe_options"; - - /** - * Determines if an entity wants to receive notifications. - * - * @return true if want to receive, false otherwise - */ - default boolean isDeliverOn() { - return readBoolean(SubscribeOptionFields.deliver.getFieldName()); - } - - /** - * Determines if notifications should be delivered as aggregations or not. - * - * @return true to aggregate, false otherwise - */ - default Boolean isDigestOn() { - return readBoolean(SubscribeOptionFields.digest.getFieldName()); - } - - /** - * Gets the minimum number of milliseconds between sending notification digests. - * - * @return The frequency in milliseconds - */ - default Integer getDigestFrequency() { - return readInteger(SubscribeOptionFields.digest_frequency.getFieldName()); - } - - /** - * Get the time at which the leased subscription will expire, or has expired. - * - * @return The expiry date - * @throws ParseException in case the date could not be parsed. - */ - default Date getExpiry() throws ParseException { - return readDate(SubscribeOptionFields.expire.getFieldName()); - } - - /** - * Determines whether the entity wants to receive an XMPP message body in - * addition to the payload format. - * - * @return true to receive the message body, false otherwise - */ - default Boolean isIncludeBody() { - return readBoolean(SubscribeOptionFields.include_body.getFieldName()); - } - - /** - * Gets the {@link PresenceState} for which an entity wants to receive - * notifications. - * - * @return the list of states - */ - default List getShowValues() { - List values = readStringValues(SubscribeOptionFields.show_values.getFieldName()); - List result = new ArrayList<>(values.size()); - - for (String state : values) { - result.add(PresenceState.valueOf(state)); - } - return result; - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/package-info.java deleted file mode 100644 index 4a119d341..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/package-info.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * - * 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. - */ - -/** - * Smack's implementation of Data Forms (XEP-0004) for PubSub. - */ -package org.jivesoftware.smackx.pubsub.form; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java index 1ae3324f8..f13bd1621 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java @@ -23,7 +23,7 @@ import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.provider.EmbeddedExtensionProvider; import org.jivesoftware.smackx.pubsub.ConfigurationEvent; -import org.jivesoftware.smackx.pubsub.form.FilledConfigureForm; +import org.jivesoftware.smackx.pubsub.ConfigureForm; import org.jivesoftware.smackx.xdata.packet.DataForm; /** @@ -38,6 +38,6 @@ public class ConfigEventProvider extends EmbeddedExtensionProvider { @Override protected FormNode createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) { - return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), (DataForm) content.iterator().next()); + return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), new Form((DataForm) content.iterator().next())); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/util/NodeUtils.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/util/NodeUtils.java index d89cfee63..9d386e609 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/util/NodeUtils.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/util/NodeUtils.java @@ -18,10 +18,10 @@ package org.jivesoftware.smackx.pubsub.util; import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smackx.pubsub.ConfigureForm; import org.jivesoftware.smackx.pubsub.FormNode; import org.jivesoftware.smackx.pubsub.PubSubElementType; -import org.jivesoftware.smackx.pubsub.form.ConfigureForm; -import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jivesoftware.smackx.xdata.Form; /** * Utility for extracting information from packets. @@ -38,7 +38,7 @@ public class NodeUtils { */ public static ConfigureForm getFormFromPacket(Stanza packet, PubSubElementType elem) { FormNode config = (FormNode) packet.getExtensionElement(elem.getElementName(), elem.getNamespace().getXmlns()); - DataForm dataForm = config.getForm(); - return new ConfigureForm(dataForm); + Form formReply = config.getForm(); + return new ConfigureForm(formReply); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/search/ReportedData.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/search/ReportedData.java index 36853164b..80c701e48 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/search/ReportedData.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/search/ReportedData.java @@ -66,7 +66,7 @@ public class ReportedData { private ReportedData(DataForm dataForm) { // Add the columns to the report based on the reported data fields for (FormField field : dataForm.getReportedData().getFields()) { - columns.add(new Column(field.getLabel(), field.getFieldName(), field.getType())); + columns.add(new Column(field.getLabel(), field.getVariable(), field.getType())); } // Add the rows to the report based on the form's items @@ -76,7 +76,7 @@ public class ReportedData { // The field is created with all the values of the data form's field List values = new ArrayList<>(); values.addAll(field.getValues()); - fieldList.add(new Field(field.getFieldName(), values)); + fieldList.add(new Field(field.getVariable(), values)); } rows.add(new Row(fieldList)); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/search/SimpleUserSearch.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/search/SimpleUserSearch.java index ac7f36901..702e494ea 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/search/SimpleUserSearch.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/search/SimpleUserSearch.java @@ -24,8 +24,8 @@ import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; +import org.jivesoftware.smackx.xdata.Form; import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.packet.DataForm; /** * SimpleUserSearch is used to support the non-dataform type of XEP 55. This provides @@ -39,14 +39,14 @@ class SimpleUserSearch extends IQ { public static final String ELEMENT = UserSearch.ELEMENT; public static final String NAMESPACE = UserSearch.NAMESPACE; - private DataForm form; + private Form form; private ReportedData data; SimpleUserSearch() { super(ELEMENT, NAMESPACE); } - public void setForm(DataForm form) { + public void setForm(Form form) { this.form = form; } @@ -65,7 +65,7 @@ class SimpleUserSearch extends IQ { StringBuilder buf = new StringBuilder(); if (form == null) { - form = DataForm.from(this); + form = Form.getFormFrom(this); } if (form == null) { @@ -73,7 +73,7 @@ class SimpleUserSearch extends IQ { } for (FormField field : form.getFields()) { - String name = field.getFieldName(); + String name = field.getVariable(); String value = getSingleValue(field); if (value.trim().length() > 0) { buf.append('<').append(name).append('>').append(value).append("'); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearch.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearch.java index bac591b78..16167b293 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearch.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearch.java @@ -31,6 +31,8 @@ import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; +import org.jivesoftware.smackx.xdata.Form; +import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.DomainBareJid; @@ -68,13 +70,13 @@ public class UserSearch extends SimpleIQ { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public DataForm getSearchForm(XMPPConnection con, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public Form getSearchForm(XMPPConnection con, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { UserSearch search = new UserSearch(); search.setType(IQ.Type.get); search.setTo(searchService); IQ response = con.createStanzaCollectorAndSend(search).nextResultOrThrow(); - return DataForm.from(response, NAMESPACE); + return Form.getFormFrom(response); } /** @@ -89,11 +91,11 @@ public class UserSearch extends SimpleIQ { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public ReportedData sendSearchForm(XMPPConnection con, DataForm searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public ReportedData sendSearchForm(XMPPConnection con, Form searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { UserSearch search = new UserSearch(); search.setType(IQ.Type.set); search.setTo(searchService); - search.addExtension(searchForm); + search.addExtension(searchForm.getDataFormToSend()); IQ response = con.createStanzaCollectorAndSend(search).nextResultOrThrow(); return ReportedData.getReportedDataFrom(response); @@ -111,7 +113,7 @@ public class UserSearch extends SimpleIQ { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public ReportedData sendSimpleSearchForm(XMPPConnection con, DataForm searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public ReportedData sendSimpleSearchForm(XMPPConnection con, Form searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { SimpleUserSearch search = new SimpleUserSearch(); search.setForm(searchForm); search.setType(IQ.Type.set); @@ -135,7 +137,11 @@ public class UserSearch extends SimpleIQ { boolean done = false; while (!done) { XmlPullParser.Event eventType = parser.next(); - if (eventType == XmlPullParser.Event.START_ELEMENT && parser.getName().equals("item")) { + if (eventType == XmlPullParser.Event.START_ELEMENT && parser.getName().equals("instructions")) { + buildDataForm(simpleUserSearch, parser.nextText(), parser, xmlEnvironment); + return simpleUserSearch; + } + else if (eventType == XmlPullParser.Event.START_ELEMENT && parser.getName().equals("item")) { simpleUserSearch.parseItems(parser); return simpleUserSearch; } @@ -158,4 +164,50 @@ public class UserSearch extends SimpleIQ { } } + private static void buildDataForm(SimpleUserSearch search, + String instructions, XmlPullParser parser, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException { + DataForm dataForm = new DataForm(DataForm.Type.form); + boolean done = false; + dataForm.setTitle("User Search"); + dataForm.addInstruction(instructions); + while (!done) { + XmlPullParser.Event eventType = parser.next(); + + if (eventType == XmlPullParser.Event.START_ELEMENT && !parser.getNamespace().equals("jabber:x:data")) { + String name = parser.getName(); + FormField.Builder field = FormField.builder(name); + + // Handle hard coded values. + if (name.equals("first")) { + field.setLabel("First Name"); + } + else if (name.equals("last")) { + field.setLabel("Last Name"); + } + else if (name.equals("email")) { + field.setLabel("Email Address"); + } + else if (name.equals("nick")) { + field.setLabel("Nickname"); + } + + field.setType(FormField.Type.text_single); + dataForm.addField(field.build()); + } + else if (eventType == XmlPullParser.Event.END_ELEMENT) { + if (parser.getName().equals("query")) { + done = true; + } + } + else if (eventType == XmlPullParser.Event.START_ELEMENT && parser.getNamespace().equals("jabber:x:data")) { + PacketParserUtils.addExtensionElement(search, parser, xmlEnvironment); + done = true; + } + } + if (search.getExtension(DataForm.class) == null) { + search.addExtension(dataForm); + } + } + + } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearchManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearchManager.java index 866d66721..85bcddd6f 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearchManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearchManager.java @@ -24,7 +24,7 @@ import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; -import org.jivesoftware.smackx.xdata.packet.DataForm; +import org.jivesoftware.smackx.xdata.Form; import org.jxmpp.jid.DomainBareJid; @@ -71,7 +71,7 @@ public class UserSearchManager { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public DataForm getSearchForm(DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public Form getSearchForm(DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { return userSearch.getSearchForm(con, searchService); } @@ -87,7 +87,7 @@ public class UserSearchManager { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public ReportedData getSearchResults(DataForm searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public ReportedData getSearchResults(Form searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { return userSearch.sendSearchForm(con, searchForm, searchService); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pep/PepEventListener.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/usertune/UserTuneListener.java similarity index 66% rename from smack-extensions/src/main/java/org/jivesoftware/smackx/pep/PepEventListener.java rename to smack-extensions/src/main/java/org/jivesoftware/smackx/usertune/UserTuneListener.java index 6abf2e77f..e020e4ec2 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pep/PepEventListener.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/usertune/UserTuneListener.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Florian Schmaus. + * Copyright 2019 Aditya Borikar. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jivesoftware.smackx.pep; +package org.jivesoftware.smackx.usertune; -import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; -import org.jxmpp.jid.EntityBareJid; +import org.jivesoftware.smackx.usertune.element.UserTuneElement; -public interface PepEventListener { +import org.jxmpp.jid.BareJid; - void onPepEvent(EntityBareJid from, E event, String id, Message carrierMessage); +public interface UserTuneListener { + void onUserTuneUpdated(BareJid jid, Message message, UserTuneElement userTuneElement); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/usertune/UserTuneManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/usertune/UserTuneManager.java index 7264b7227..415c53dcc 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/usertune/UserTuneManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/usertune/UserTuneManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2019 Aditya Borikar, 2020 Florian Schmaus. + * Copyright 2019 Aditya Borikar. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,23 +16,34 @@ */ package org.jivesoftware.smackx.usertune; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import org.jivesoftware.smack.AsyncButOrdered; import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotLoggedInException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; -import org.jivesoftware.smackx.pep.PepEventListener; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.pep.PepListener; import org.jivesoftware.smackx.pep.PepManager; +import org.jivesoftware.smackx.pubsub.EventElement; +import org.jivesoftware.smackx.pubsub.ItemsExtension; import org.jivesoftware.smackx.pubsub.PayloadItem; import org.jivesoftware.smackx.pubsub.PubSubException.NotALeafNodeException; import org.jivesoftware.smackx.usertune.element.UserTuneElement; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; + /** * Entry point for Smacks API for XEP-0118: User Tune. *
@@ -40,7 +51,7 @@ import org.jivesoftware.smackx.usertune.element.UserTuneElement; *
* To stop publishing a UserTune, please use {@link #clearUserTune()} method. This will send a disabling publish signal. *
- * To add a UserTune listener in order to remain updated with other users UserTune, use {@link #addUserTuneListener(PepEventListener)} method. + * To add a UserTune listener in order to remain updated with other users UserTune, use {@link #addUserTuneListener(UserTuneListener)} method. *
* To link a UserTuneElement with {@link Message}, use 'message.addExtension(userTuneElement)'. *
@@ -52,9 +63,15 @@ import org.jivesoftware.smackx.usertune.element.UserTuneElement; public final class UserTuneManager extends Manager { public static final String USERTUNE_NODE = "http://jabber.org/protocol/tune"; + public static final String USERTUNE_NOTIFY = USERTUNE_NODE + "+notify"; private static final Map INSTANCES = new WeakHashMap<>(); + private static boolean ENABLE_USER_TUNE_NOTIFICATIONS_BY_DEFAULT = true; + + private final Set userTuneListeners = new CopyOnWriteArraySet<>(); + private final AsyncButOrdered asyncButOrdered = new AsyncButOrdered<>(); + private final ServiceDiscoveryManager serviceDiscoveryManager; private final PepManager pepManager; public static synchronized UserTuneManager getInstanceFor(XMPPConnection connection) throws NotLoggedInException { @@ -66,9 +83,46 @@ public final class UserTuneManager extends Manager { return manager; } - private UserTuneManager(XMPPConnection connection) { + private UserTuneManager(XMPPConnection connection) throws NotLoggedInException { super(connection); pepManager = PepManager.getInstanceFor(connection); + pepManager.addPepListener(new PepListener() { + @Override + public void eventReceived(EntityBareJid from, EventElement event, Message message) { + if (!USERTUNE_NODE.equals(event.getEvent().getNode())) { + return; + } + + final BareJid contact = from.asBareJid(); + asyncButOrdered.performAsyncButOrdered(contact, () -> { + ItemsExtension itemsExtension = (ItemsExtension) event.getEvent(); + List items = itemsExtension.getExtensions(); + @SuppressWarnings("unchecked") + PayloadItem payload = (PayloadItem) items.get(0); + UserTuneElement tune = payload.getPayload(); + + for (UserTuneListener listener : userTuneListeners) { + listener.onUserTuneUpdated(contact, message, tune); + } + }); + } + }); + serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); + if (ENABLE_USER_TUNE_NOTIFICATIONS_BY_DEFAULT) { + enableUserTuneNotifications(); + } + } + + public static void setUserTuneNotificationsEnabledByDefault(boolean bool) { + ENABLE_USER_TUNE_NOTIFICATIONS_BY_DEFAULT = bool; + } + + public void enableUserTuneNotifications() { + serviceDiscoveryManager.addFeature(USERTUNE_NOTIFY); + } + + public void disableUserTuneNotifications() { + serviceDiscoveryManager.removeFeature(USERTUNE_NOTIFY); } public void clearUserTune() throws NotLoggedInException, NotALeafNodeException, NoResponseException, NotConnectedException, XMPPErrorException, InterruptedException { @@ -80,11 +134,11 @@ public final class UserTuneManager extends Manager { pepManager.publish(USERTUNE_NODE, new PayloadItem<>(userTuneElement)); } - public boolean addUserTuneListener(PepEventListener listener) { - return pepManager.addPepEventListener(USERTUNE_NODE, UserTuneElement.class, listener); + public boolean addUserTuneListener(UserTuneListener listener) { + return userTuneListeners.add(listener); } - public boolean removeUserTuneListener(PepEventListener listener) { - return pepManager.removePepEventListener(listener); + public boolean removeUserTuneListener(UserTuneListener listener) { + return userTuneListeners.remove(listener); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java deleted file mode 100644 index 31c778a87..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java +++ /dev/null @@ -1,92 +0,0 @@ -/** - * - * 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.smackx.xdata; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; - -import org.jivesoftware.smack.util.CollectionUtil; - -import org.jxmpp.util.XmppDateTime; - -public class AbstractMultiFormField extends FormField { - - private final List values; - - protected AbstractMultiFormField(Builder builder) { - super(builder); - values = CollectionUtil.cloneAndSeal(builder.values); - } - - @Override - public final List getValues() { - return values; - } - - - public abstract static class Builder> - extends FormField.Builder { - - private List values; - - protected Builder(AbstractMultiFormField formField) { - super(formField); - values = CollectionUtil.newListWith(formField.getValues()); - } - - protected Builder(String fieldName, FormField.Type type) { - super(fieldName, type); - } - - private void ensureValuesAreInitialized() { - if (values == null) { - values = new ArrayList<>(); - } - } - - @Override - protected void resetInternal() { - values = null; - } - - public abstract B addValue(CharSequence value); - - public B addValueVerbatim(CharSequence value) { - ensureValuesAreInitialized(); - - values.add(value.toString()); - return getThis(); - } - - public final B addValue(Date date) { - String dateString = XmppDateTime.formatXEP0082Date(date); - return addValueVerbatim(dateString); - } - - public final B addValues(Collection values) { - ensureValuesAreInitialized(); - - for (CharSequence value : values) { - this.values.add(value.toString()); - } - - return getThis(); - } - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java deleted file mode 100644 index 27fca4cde..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * - * 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.smackx.xdata; - -import java.net.URL; -import java.util.Date; - -import org.jxmpp.util.XmppDateTime; - -public class AbstractSingleStringValueFormField extends SingleValueFormField { - - private final String value; - - protected AbstractSingleStringValueFormField(Builder builder) { - super(builder); - value = builder.value; - } - - @Override - public final String getValue() { - return value; - } - - public final Integer getValueAsInt() { - if (value == null) { - return null; - } - return Integer.valueOf(value); - } - - public abstract static class Builder> extends FormField.Builder { - - private String value; - - protected Builder(AbstractSingleStringValueFormField abstractSingleFormField) { - super(abstractSingleFormField); - value = abstractSingleFormField.getValue(); - } - - protected Builder(String fieldName, FormField.Type type) { - super(fieldName, type); - } - - @Override - protected void resetInternal() { - value = null; - } - - /** - * Set the value. - * - * @param value the value to set. - * @return a reference to this builder. - * @deprecated use {@link #setValue(CharSequence)} instead. - */ - @Deprecated - // TODO: Remove in Smack 4.6. - public B addValue(CharSequence value) { - return setValue(value); - } - - public B setValue(CharSequence value) { - this.value = value.toString(); - return getThis(); - } - - public B setValue(Enum value) { - this.value = value.toString(); - return getThis(); - } - - public B setValue(int value) { - this.value = Integer.toString(value); - return getThis(); - } - - public B setValue(URL value) { - return setValue(value.toString()); - } - - public B setValue(Date date) { - String dateString = XmppDateTime.formatXEP0082Date(date); - return setValue(dateString); - } - } -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java deleted file mode 100644 index 2b036b162..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java +++ /dev/null @@ -1,98 +0,0 @@ -/** - * - * 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.smackx.xdata; - -import org.jivesoftware.smack.util.ParserUtils; - -public class BooleanFormField extends SingleValueFormField { - - private final Boolean value; - - protected BooleanFormField(Builder builder) { - super(builder); - value = builder.value; - } - - @Override - public String getValue() { - if (value == null) { - return null; - } - return value.toString(); - } - - public Boolean getValueAsBoolean() { - return value; - } - - public Builder asBuilder() { - return new Builder(this); - } - - public static final class Builder extends FormField.Builder { - private Boolean value; - - private Builder(BooleanFormField booleanFormField) { - super(booleanFormField); - value = booleanFormField.value; - } - - Builder(String fieldName) { - super(fieldName, FormField.Type.bool); - } - - @Override - protected void resetInternal() { - value = null; - } - - /** - * Set the value. - * - * @param value the value to set. - * @return a reference to this builder. - * @deprecated use {@link #setValue(CharSequence)} instead. - */ - @Deprecated - // TODO: Remove in Smack 4.6. - public Builder addValue(CharSequence value) { - return setValue(value); - } - - public Builder setValue(CharSequence value) { - boolean valueBoolean = ParserUtils.parseXmlBoolean(value.toString()); - return setValue(valueBoolean); - } - - public Builder setValue(boolean value) { - this.value = value; - return this; - } - - @Override - public BooleanFormField build() { - return new BooleanFormField(this); - } - - @Override - public Builder getThis() { - return this; - } - - } - -} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/Form.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/Form.java new file mode 100644 index 000000000..9e5bb999a --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/Form.java @@ -0,0 +1,517 @@ +/** + * + * Copyright 2003-2007 Jive Software. + * + * 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.smackx.xdata; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.StringTokenizer; + +import org.jivesoftware.smack.packet.Stanza; + +import org.jivesoftware.smackx.xdata.packet.DataForm; + +/** + * Represents a Form for gathering data. The form could be of the following types: + *

    + *
  • form → Indicates a form to fill out.
  • + *
  • submit → The form is filled out, and this is the data that is being returned from + * the form.
  • + *
  • cancel → The form was cancelled. Tell the asker that piece of information.
  • + *
  • result → Data results being returned from a search, or some other query.
  • + *
+ * + * Depending of the form's type different operations are available. For example, it's only possible + * to set answers if the form is of type "submit". + * + * @see XEP-0004 Data Forms + * + * @author Gaston Dombiak + */ +public class Form { + + private DataForm dataForm; + + /** + * Returns a new ReportedData if the stanza is used for gathering data and includes an + * extension that matches the elementName and namespace "x","jabber:x:data". + * + * @param packet the stanza used for gathering data. + * @return the data form parsed from the stanza or null if there was not + * a form in the packet. + */ + public static Form getFormFrom(Stanza packet) { + // Check if the packet includes the DataForm extension + DataForm dataForm = DataForm.from(packet); + if (dataForm != null) { + if (dataForm.getReportedData() == null) + return new Form(dataForm); + } + // Otherwise return null + return null; + } + + /** + * Creates a new Form that will wrap an existing DataForm. The wrapped DataForm must be + * used for gathering data. + * + * @param dataForm the data form used for gathering data. + */ + public Form(DataForm dataForm) { + this.dataForm = dataForm; + } + + /** + * Creates a new Form of a given type from scratch. + * + * @param type the form's type (e.g. form, submit, cancel, result). + */ + public Form(DataForm.Type type) { + this.dataForm = new DataForm(type); + } + + /** + * Adds a new field to complete as part of the form. + * + * @param field the field to complete. + */ + public void addField(FormField field) { + dataForm.addField(field); + } + + /** + * Sets a new String value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised.

+ * + * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you + * can use this message where the String value is the String representation of the object. + * + * @param variable the variable name that was completed. + * @param value the String value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type.. + */ + public void setAnswer(String variable, CharSequence value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + switch (field.getType()) { + case text_multi: + case text_private: + case text_single: + case jid_single: + case hidden: + break; + default: + throw new IllegalArgumentException("This field is not of type String."); + } + setAnswer(field, value); + } + + /** + * Sets a new int value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the int value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, int value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + validateThatFieldIsText(field); + setAnswer(field, value); + } + + /** + * Sets a new long value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the long value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, long value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + validateThatFieldIsText(field); + setAnswer(field, value); + } + + /** + * Sets a new float value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the float value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, float value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + validateThatFieldIsText(field); + setAnswer(field, value); + } + + /** + * Sets a new double value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the double value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, double value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + validateThatFieldIsText(field); + setAnswer(field, value); + } + + private static void validateThatFieldIsText(FormField field) { + switch (field.getType()) { + case text_multi: + case text_private: + case text_single: + break; + default: + throw new IllegalArgumentException("This field is not of type text (multi, private or single)."); + } + } + + /** + * Sets a new boolean value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the boolean value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, boolean value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (field.getType() != FormField.Type.bool) { + throw new IllegalArgumentException("This field is not of type boolean."); + } + setAnswer(field, Boolean.toString(value)); + } + + /** + * Sets a new Object value to a given form's field. In fact, the object representation + * (i.e. #toString) will be the actual value of the field.

+ * + * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you + * will need to use {@link #setAnswer(String, String)} where the String value is the + * String representation of the object.

+ * + * Before setting the new value to the field we will check if the form is of type submit. If + * the form isn't of type submit means that it's not possible to complete the form and an + * exception will be thrown. + * + * @param field the form field that was completed. + * @param value the Object value that was answered. The object representation will be the + * actual value. + * @throws IllegalStateException if the form is not of type "submit". + */ + private void setAnswer(FormField field, Object value) { + if (!isSubmitType()) { + throw new IllegalStateException("Cannot set an answer if the form is not of type " + + "\"submit\""); + } + + FormField filledOutfield = field.buildAnswer().addValue(value.toString()).build(); + dataForm.replaceField(filledOutfield); + } + + /** + * Sets a new values to a given form's field. The field whose variable matches the requested + * variable will be completed with the specified values. If no field could be found for + * the specified variable then an exception will be raised.

+ * + * The Objects contained in the List could be of any type. The String representation of them + * (i.e. #toString) will be actually used when sending the answer to the server. + * + * @param variable the variable that was completed. + * @param values the values that were answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + */ + public void setAnswer(String variable, Collection values) { + if (!isSubmitType()) { + throw new IllegalStateException("Cannot set an answer if the form is not of type " + + "\"submit\""); + } + FormField field = getField(variable); + if (field != null) { + // Check that the field can accept a collection of values + switch (field.getType()) { + case jid_multi: + case list_multi: + case list_single: + case text_multi: + case hidden: + break; + default: + throw new IllegalArgumentException("This field only accept list of values."); + } + + FormField filledOutfield = field.buildAnswer().addValues(values).build(); + dataForm.replaceField(filledOutfield); + } + else { + throw new IllegalArgumentException("Couldn't find a field for the specified variable."); + } + } + + /** + * Sets the default value as the value of a given form's field. The field whose variable matches + * the requested variable will be completed with its default value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable to complete with its default value. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + */ + public void setDefaultAnswer(String variable) { + if (!isSubmitType()) { + throw new IllegalStateException("Cannot set an answer if the form is not of type " + + "\"submit\""); + } + FormField field = getField(variable); + if (field != null) { + FormField.Builder filledOutFormFieldBuilder = field.buildAnswer(); + // Set the default value + for (CharSequence value : field.getValues()) { + filledOutFormFieldBuilder.addValue(value); + } + dataForm.replaceField(filledOutFormFieldBuilder.build()); + } + else { + throw new IllegalArgumentException("Couldn't find a field for the specified variable."); + } + } + + /** + * Returns a List of the fields that are part of the form. + * + * @return a List of the fields that are part of the form. + */ + public List getFields() { + return dataForm.getFields(); + } + + /** + * Returns the field of the form whose variable matches the specified variable. + * The fields of type FIXED will never be returned since they do not specify a + * variable. + * + * @param variable the variable to look for in the form fields. + * @return the field of the form whose variable matches the specified variable. + */ + public FormField getField(String variable) { + return dataForm.getField(variable); + } + + /** + * Check if a field with the given variable exists. + * + * @param variable the variable to check for. + * @return true if a field with the variable exists, false otherwise. + * @since 4.2 + */ + public boolean hasField(String variable) { + return dataForm.hasField(variable); + } + + /** + * Returns the instructions that explain how to fill out the form and what the form is about. + * + * @return instructions that explain how to fill out the form. + */ + public String getInstructions() { + StringBuilder sb = new StringBuilder(); + // Join the list of instructions together separated by newlines + for (Iterator it = dataForm.getInstructions().iterator(); it.hasNext();) { + sb.append(it.next()); + // If this is not the last instruction then append a newline + if (it.hasNext()) { + sb.append('\n'); + } + } + return sb.toString(); + } + + + /** + * Returns the description of the data. It is similar to the title on a web page or an X + * window. You can put a title on either a form to fill out, or a set of data results. + * + * @return description of the data. + */ + public String getTitle() { + return dataForm.getTitle(); + } + + + /** + * Returns the meaning of the data within the context. The data could be part of a form + * to fill out, a form submission or data results. + * + * @return the form's type. + */ + public DataForm.Type getType() { + return dataForm.getType(); + } + + + /** + * Sets instructions that explain how to fill out the form and what the form is about. + * + * @param instructions instructions that explain how to fill out the form. + */ + public void setInstructions(String instructions) { + // Split the instructions into multiple instructions for each existent newline + ArrayList instructionsList = new ArrayList<>(); + StringTokenizer st = new StringTokenizer(instructions, "\n"); + while (st.hasMoreTokens()) { + instructionsList.add(st.nextToken()); + } + // Set the new list of instructions + dataForm.setInstructions(instructionsList); + + } + + + /** + * Sets the description of the data. It is similar to the title on a web page or an X window. + * You can put a title on either a form to fill out, or a set of data results. + * + * @param title description of the data. + */ + public void setTitle(String title) { + dataForm.setTitle(title); + } + + /** + * Returns a DataForm that serves to send this Form to the server. If the form is of type + * submit, it may contain fields with no value. These fields will be removed since they only + * exist to assist the user while editing/completing the form in a UI. + * + * @return the wrapped DataForm. + */ + public DataForm getDataFormToSend() { + if (isSubmitType()) { + // Create a new DataForm that contains only the answered fields + DataForm dataFormToSend = new DataForm(getType()); + for (FormField field : getFields()) { + if (!field.getValues().isEmpty()) { + dataFormToSend.addField(field); + } + } + return dataFormToSend; + } + return dataForm; + } + + /** + * Returns true if the form is a form to fill out. + * + * @return if the form is a form to fill out. + */ + private boolean isFormType() { + return DataForm.Type.form == dataForm.getType(); + } + + /** + * Returns true if the form is a form to submit. + * + * @return if the form is a form to submit. + */ + private boolean isSubmitType() { + return DataForm.Type.submit == dataForm.getType(); + } + + /** + * Returns a new Form to submit the completed values. The new Form will include all the fields + * of the original form except for the fields of type FIXED. Only the HIDDEN fields will + * include the same value of the original form. The other fields of the new form MUST be + * completed. If a field remains with no answer when sending the completed form, then it won't + * be included as part of the completed form.

+ * + * The reason why the fields with variables are included in the new form is to provide a model + * for binding with any UI. This means that the UIs will use the original form (of type + * "form") to learn how to render the form, but the UIs will bind the fields to the form of + * type submit. + * + * @return a Form to submit the completed values. + */ + public Form createAnswerForm() { + if (!isFormType()) { + throw new IllegalStateException("Only forms of type \"form\" could be answered"); + } + // Create a new Form + Form form = new Form(DataForm.Type.submit); + for (FormField field : getFields()) { + // Add to the new form any type of field that includes a variable. + // Note: The fields of type FIXED are the only ones that don't specify a variable + if (field.getVariable() != null) { + FormField.Builder newField = FormField.builder(field.getVariable()); + newField.setType(field.getType()); + form.addField(newField.build()); + // Set the answer ONLY to the hidden fields + if (field.getType() == FormField.Type.hidden) { + // Since a hidden field could have many values we need to collect them + // in a list + List values = new ArrayList<>(); + values.addAll(field.getValues()); + form.setAnswer(field.getVariable(), values); + } + } + } + return form; + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java index 4bc28a7ef..525d8fa75 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2019-2020 Florian Schmaus. + * Copyright 2003-2007 Jive Software, 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. @@ -33,7 +33,6 @@ import org.jivesoftware.smack.util.CollectionUtil; import org.jivesoftware.smack.util.EqualsUtil; import org.jivesoftware.smack.util.HashCode; import org.jivesoftware.smack.util.MultiMap; -import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smackx.xdata.packet.DataForm; @@ -44,14 +43,10 @@ import org.jxmpp.util.XmppDateTime; * Represents a field of a form. The field could be used to represent a question to complete, * a completed question or a data returned from a search. The exact interpretation of the field * depends on the context where the field is used. - *

- * Fields have a name, which is stored in the 'var' attribute of the field's XML representation. - * Field instances of all types, except of type "fixed" must have a name. - *

* * @author Gaston Dombiak */ -public abstract class FormField implements FullyQualifiedElement { +public final class FormField implements FullyQualifiedElement { public static final String ELEMENT = "field"; @@ -159,7 +154,7 @@ public abstract class FormField implements FullyQualifiedElement { /** * The field's name. Put as value in the 'var' attribute of <field/>. */ - private final String fieldName; + private final String variable; private final String label; @@ -173,6 +168,8 @@ public abstract class FormField implements FullyQualifiedElement { * The following four fields are cache values which are represented as child elements of and hence also * appear in formFieldChildElements. */ + private final List