From 77e26fc5754a102d37372b5a977d718a36294dca Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 13 May 2020 20:14:41 +0200 Subject: [PATCH] Re-work data form API Apply builder pattern to form fields and replace getVariable() with getFieldName(). Refer to the field name as "field name" instead of "variable" everyone, just as XEP-0004 does. Improve the high-level form API: introduce FilledForm and FillableForm which perform stronger validation and consistency checks. Also add FormFieldRegistry to enable processing of 'submit' forms where the form field types are omitted. Smack also now does omit the form field type declaration on 'submit' type forms, as it is allowed by XEP-0004. --- documentation/extensions/index.md | 1 + .../smack/parsing/SmackParsingException.java | 6 +- .../smack/util/CollectionUtil.java | 17 + .../jivesoftware/smack/util/StringUtils.java | 9 + .../smack/util/XmlStringBuilder.java | 11 + .../jivesoftware/smackx/mam/MamManager.java | 25 +- .../element/EnablePushNotificationsIQ.java | 14 +- .../jivesoftware/smackx/mam/FiltersTest.java | 4 +- .../smackx/mam/MamQueryIQProviderTest.java | 2 +- .../org/jivesoftware/smackx/mam/MamTest.java | 3 +- .../jivesoftware/smackx/mam/PagingTest.java | 2 +- .../smackx/mam/QueryArchiveTest.java | 2 +- .../smackx/mam/ResultsLimitTest.java | 4 +- .../smackx/mam/RetrieveFormFieldsTest.java | 15 +- .../admin/ServiceAdministrationManager.java | 8 +- .../smackx/caps/EntityCapsManager.java | 51 +- .../smackx/commands/AdHocCommand.java | 20 +- .../smackx/commands/AdHocCommandManager.java | 9 +- .../smackx/commands/RemoteCommand.java | 22 +- .../filetransfer/FileTransferNegotiator.java | 18 +- .../smackx/filetransfer/StreamNegotiator.java | 9 +- .../smackx/formtypes/FormFieldRegistry.java | 158 +++++ .../smackx/formtypes/package-info.java | 21 + .../smackx/muc/MucConfigFormManager.java | 39 +- .../smackx/muc/MultiUserChat.java | 48 +- .../smackx/muc/MultiUserChatConstants.java | 23 + .../org/jivesoftware/smackx/muc/RoomInfo.java | 10 +- .../smackx/offline/OfflineMessageManager.java | 14 +- .../smackx/pubsub/ConfigurationEvent.java | 12 +- .../smackx/pubsub/ConfigureForm.java | 643 ------------------ .../smackx/pubsub/ConfigureNodeFields.java | 5 +- .../jivesoftware/smackx/pubsub/FormNode.java | 12 +- .../org/jivesoftware/smackx/pubsub/Item.java | 1 + .../jivesoftware/smackx/pubsub/ItemReply.java | 4 +- .../jivesoftware/smackx/pubsub/LeafNode.java | 1 + .../org/jivesoftware/smackx/pubsub/Node.java | 27 +- .../smackx/pubsub/NotificationType.java | 4 +- .../smackx/pubsub/PayloadItem.java | 1 + .../smackx/pubsub/PresenceState.java | 2 + .../smackx/pubsub/PubSubManager.java | 19 +- .../smackx/pubsub/PublishModel.java | 4 +- .../smackx/pubsub/SubscribeForm.java | 214 ------ .../smackx/pubsub/form/ConfigureForm.java | 33 + .../pubsub/form/ConfigureFormReader.java | 292 ++++++++ .../pubsub/form/FillableConfigureForm.java | 314 +++++++++ .../pubsub/form/FillableSubscribeForm.java | 95 +++ .../pubsub/form/FilledConfigureForm.java | 29 + .../pubsub/form/FilledSubscribeForm.java | 29 + .../smackx/pubsub/form/SubscribeForm.java | 29 + .../pubsub/form/SubscribeFormReader.java | 95 +++ .../smackx/pubsub/form/package-info.java | 21 + .../pubsub/provider/ConfigEventProvider.java | 4 +- .../pubsub/provider/FormNodeProvider.java | 3 +- .../smackx/pubsub/util/NodeUtils.java | 8 +- .../smackx/search/ReportedData.java | 4 +- .../smackx/search/SimpleUserSearch.java | 10 +- .../smackx/search/UserSearch.java | 64 +- .../smackx/search/UserSearchManager.java | 6 +- .../smackx/xdata/AbstractMultiFormField.java | 92 +++ .../AbstractSingleStringValueFormField.java | 100 +++ .../smackx/xdata/BooleanFormField.java | 98 +++ .../org/jivesoftware/smackx/xdata/Form.java | 517 -------------- .../jivesoftware/smackx/xdata/FormField.java | 416 +++++------ .../smackx/xdata/FormFieldChildElement.java | 7 +- .../smackx/xdata/FormFieldWithOptions.java | 47 ++ .../smackx/xdata/JidMultiFormField.java | 94 +++ .../smackx/xdata/JidSingleFormField.java | 71 ++ .../smackx/xdata/ListMultiFormField.java | 88 +++ .../smackx/xdata/ListSingleFormField.java | 87 +++ .../smackx/xdata/SingleValueFormField.java | 48 ++ .../smackx/xdata/TextMultiFormField.java | 73 ++ .../smackx/xdata/TextSingleFormField.java | 51 ++ .../smackx/xdata/XDataManager.java | 10 +- .../smackx/xdata/form/FillableForm.java | 267 ++++++++ .../smackx/xdata/form/FilledForm.java | 97 +++ .../jivesoftware/smackx/xdata/form/Form.java | 44 ++ .../smackx/xdata/form/FormReader.java | 90 +++ .../smackx/xdata/form/FormWriter.java | 25 + .../smackx/xdata/form/package-info.java | 21 + .../smackx/xdata/packet/DataForm.java | 486 ++++++++----- .../xdata/provider/DataFormProvider.java | 257 ++++++- .../smackx/xdata/provider/OptionProvider.java | 63 -- .../xdata/provider/RequiredProvider.java | 43 -- .../smackx/xdata/provider/ValueProvider.java | 43 -- .../packet/ValidateElement.java | 51 +- .../smackx/caps/EntityCapsManagerTest.java | 122 ++-- .../jivesoftware/smackx/muc/RoomInfoTest.java | 19 +- .../smackx/pubsub/ConfigureFormTest.java | 16 - .../smackx/xdata/FormFieldTest.java | 38 ++ .../smackx/xdata/packet/DataFormTest.java | 66 +- .../DataValidationHelperTest.java | 15 +- .../smackx/pubsub/PubSubIntegrationTest.java | 7 +- .../jivesoftware/smackx/xdata/FormTest.java | 64 +- .../smackx/workgroup/agent/AgentSession.java | 5 +- .../agent/TranscriptSearchManager.java | 11 +- .../smackx/workgroup/user/Workgroup.java | 31 +- .../smackx/ox/util/OpenPgpPubSubUtil.java | 6 +- 97 files changed, 3809 insertions(+), 2427 deletions(-) create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/package-info.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatConstants.java delete mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureForm.java delete mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/ConfigureForm.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/ConfigureFormReader.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FillableConfigureForm.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FillableSubscribeForm.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FilledConfigureForm.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FilledSubscribeForm.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/SubscribeForm.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/SubscribeFormReader.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/package-info.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java delete mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/Form.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormFieldWithOptions.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidMultiFormField.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidSingleFormField.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/ListMultiFormField.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/ListSingleFormField.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/TextMultiFormField.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/TextSingleFormField.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FillableForm.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FilledForm.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/Form.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormWriter.java create mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/package-info.java delete mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/OptionProvider.java delete mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/RequiredProvider.java delete mode 100644 smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/ValueProvider.java create mode 100644 smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index f84b5c025..91f291ca4 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -51,6 +51,7 @@ 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. | 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 82502736f..08b4586fb 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 Florian Schmaus + * Copyright 2019-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,10 @@ 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 eb76ef3be..b35dfb4a0 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,9 +18,12 @@ 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 { @@ -59,6 +62,20 @@ 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/StringUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java index b2958395b..c966b2d34 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,8 +20,10 @@ 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; @@ -591,4 +593,11 @@ 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 145b1d3ec..c483db913 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,6 +77,10 @@ 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); @@ -493,6 +497,13 @@ 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-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java index 9bbbe301e..3398df23c 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,8 +278,9 @@ public final class MamManager extends Manager { if (dataForm != null) { return dataForm; } - dataForm = getNewMamForm(); - dataForm.addFields(formFields.values()); + DataForm.Builder dataFormBuilder = getNewMamForm(); + dataFormBuilder.addFields(formFields.values()); + dataForm = dataFormBuilder.build(); return dataForm; } @@ -330,7 +331,7 @@ public final class MamManager extends Manager { } FormField formField = getWithFormField(withJid); - formFields.put(formField.getVariable(), formField); + formFields.put(formField.getFieldName(), formField); return this; } @@ -341,9 +342,9 @@ public final class MamManager extends Manager { } FormField formField = FormField.builder(FORM_FIELD_START) - .addValue(start) + .setValue(start) .build(); - formFields.put(formField.getVariable(), formField); + formFields.put(formField.getFieldName(), formField); FormField endFormField = formFields.get(FORM_FIELD_END); if (endFormField != null) { @@ -369,9 +370,9 @@ public final class MamManager extends Manager { } FormField formField = FormField.builder(FORM_FIELD_END) - .addValue(end) + .setValue(end) .build(); - formFields.put(formField.getVariable(), formField); + formFields.put(formField.getFieldName(), formField); FormField startFormField = formFields.get(FORM_FIELD_START); if (startFormField != null) { @@ -418,7 +419,7 @@ public final class MamManager extends Manager { } public Builder withAdditionalFormField(FormField formField) { - formFields.put(formField.getVariable(), formField); + formFields.put(formField.getFieldName(), formField); return this; } @@ -483,7 +484,7 @@ public final class MamManager extends Manager { private static FormField getWithFormField(Jid withJid) { return FormField.builder(FORM_FIELD_WITH) - .addValue(withJid.toString()) + .setValue(withJid.toString()) .build(); } @@ -718,9 +719,9 @@ public final class MamManager extends Manager { throw new SmackException.FeatureNotSupportedException(ADVANCED_CONFIG_NODE, archiveAddress); } - private static DataForm getNewMamForm() { - FormField field = FormField.hiddenFormType(MamElements.NAMESPACE); - DataForm form = new DataForm(DataForm.Type.submit); + private static DataForm.Builder getNewMamForm() { + FormField field = FormField.buildHiddenFormType(MamElements.NAMESPACE); + DataForm.Builder form = DataForm.builder(); form.addField(field); return form; } 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 7c86584d8..428b41b67 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,6 +24,7 @@ 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; @@ -98,24 +99,23 @@ public class EnablePushNotificationsIQ extends IQ { xml.rightAngleBracket(); if (publishOptions != null) { - DataForm dataForm = new DataForm(DataForm.Type.submit); + DataForm.Builder dataForm = DataForm.builder(); // 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.Builder formTypeField = FormField.builder("FORM_TYPE"); - formTypeField.addValue(PubSub.NAMESPACE + "#publish-options"); - dataForm.addField(formTypeField.build()); + FormField formTypeField = FormField.buildHiddenFormType(PubSub.NAMESPACE + "#publish-options"); + dataForm.addField(formTypeField); Iterator> publishOptionsIterator = publishOptions.entrySet().iterator(); while (publishOptionsIterator.hasNext()) { Map.Entry pairVariableValue = publishOptionsIterator.next(); - FormField.Builder field = FormField.builder(pairVariableValue.getKey()); - field.addValue(pairVariableValue.getValue()); + TextSingleFormField.Builder field = FormField.builder(pairVariableValue.getKey()); + field.setValue(pairVariableValue.getValue()); dataForm.addField(field.build()); } - xml.append(dataForm); + xml.append(dataForm.build()); } return xml; 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 738906053..212c37e89 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 Florian Schmaus + * Copyright 2016 Fernando Ramirez, 2018-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. @@ -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 f39411a00..c9f3be99e 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).getVariable(), "urn:example:xmpp:free-text-search"); + assertEquals(fields2.get(4).getFieldName(), "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 6dd2f8e73..33e9df1a3 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,7 +49,8 @@ public class MamTest extends SmackTestSuite { IllegalArgumentException, InvocationTargetException { Method methodGetNewMamForm = MamManager.class.getDeclaredMethod("getNewMamForm"); methodGetNewMamForm.setAccessible(true); - return (DataForm) methodGetNewMamForm.invoke(mamManager); + DataForm.Builder dataFormBuilder = (DataForm.Builder) methodGetNewMamForm.invoke(mamManager); + return dataFormBuilder.build(); } } 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 d0919dbcf..1f1450efd 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 bc4360eef..8fe38eeec 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 9adebb3d5..157abc520 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-2019 Florian Schmaus + * Copyright 2016 Fernando Ramirez, 2018-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. @@ -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 264319c0e..87000a6e8 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-2019 Florian Schmaus + * Copyright 2016 Fernando Ramirez, 2018-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. @@ -28,16 +28,17 @@ 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" + "" - + "" + "Hi2" + "" + + "" + "one@exampleone.org" + "" + ""; @Test @@ -51,13 +52,11 @@ public class RetrieveFormFieldsTest extends MamTest { @Test public void checkAddAdditionalFieldsStanza() throws Exception { FormField field1 = FormField.builder("urn:example:xmpp:free-text-search") - .setType(FormField.Type.text_single) - .addValue("Hi") + .setValue("Hi") .build(); - FormField field2 = FormField.builder("urn:example:xmpp:stanza-content") - .setType(FormField.Type.jid_single) - .addValue("Hi2") + FormField field2 = FormField.jidSingleBuilder("urn:example:xmpp:stanza-content") + .setValue(JidTestUtil.BARE_JID_1) .build(); MamQueryArgs mamQueryArgs = MamQueryArgs.builder() 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 a333b2027..78ce687a6 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-2019 Florian Schmaus + * Copyright 2016-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,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; +import org.jivesoftware.smackx.xdata.form.FillableForm; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.Jid; @@ -72,7 +72,7 @@ public class ServiceAdministrationManager extends Manager { RemoteCommand command = addUser(); command.execute(); - Form answerForm = command.getForm().createAnswerForm(); + FillableForm answerForm = new FillableForm(command.getForm()); answerForm.setAnswer("accountjid", userJid); answerForm.setAnswer("password", password); @@ -101,7 +101,7 @@ public class ServiceAdministrationManager extends Manager { RemoteCommand command = deleteUser(); command.execute(); - Form answerForm = command.getForm().createAnswerForm(); + FillableForm answerForm = new FillableForm(command.getForm()); 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 008baa3bd..5407d11e3 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-2019 Florian Schmaus + * Copyright © 2009 Jonas Ådahl, 2011-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. @@ -22,11 +22,13 @@ 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; @@ -49,7 +51,6 @@ 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; @@ -601,7 +602,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; @@ -613,27 +614,29 @@ public final class EntityCapsManager extends Manager { } /** + * Verify that the given discovery info is not ill-formed. * - * @param info TODO javadoc me please - * @return true if the stanza extensions is ill-formed + * @param info the discovery info to verify. + * @return true if the stanza extensions is not ill-formed */ - 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); - } - } + 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; } } - return false; + + return true; } protected static CapsVersionAndHash generateVerificationString(DiscoverInfoView discoverInfo) { @@ -718,12 +721,12 @@ public final class EntityCapsManager extends Manager { SortedSet fs = new TreeSet<>(new Comparator() { @Override public int compare(FormField f1, FormField f2) { - return f1.getVariable().compareTo(f2.getVariable()); + return f1.getFieldName().compareTo(f2.getFieldName()); } }); for (FormField f : extendedInfo.getFields()) { - if (!f.getVariable().equals("FORM_TYPE")) { + if (!f.getFieldName().equals("FORM_TYPE")) { fs.add(f); } } @@ -739,7 +742,7 @@ public final class EntityCapsManager extends Manager { // 3. For each element, append the XML character data, // followed by the '<' character. for (FormField f : fs) { - sb.append(f.getVariable()); + sb.append(f.getFieldName()); sb.append('<'); formFieldValuesToCaps(f.getValues(), sb); } @@ -763,7 +766,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 32f6eea41..c7815fa8c 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,7 +24,8 @@ 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; +import org.jivesoftware.smackx.xdata.form.FillableForm; +import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.Jid; @@ -188,13 +189,8 @@ public abstract class AdHocCommand { * @return the form of the current stage to fill out or the result of the * execution. */ - public Form getForm() { - if (data.getForm() == null) { - return null; - } - else { - return new Form(data.getForm()); - } + public DataForm getForm() { + return data.getForm(); } /** @@ -205,8 +201,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(Form form) { - data.setForm(form.getDataFormToSend()); + protected void setForm(DataForm form) { + data.setForm(form); } /** @@ -234,7 +230,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(Form response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; + public abstract void next(FillableForm response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; /** * Completes the command execution with the information provided in the @@ -250,7 +246,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(Form response) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; + public abstract void complete(FillableForm 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 588ca2116..0f4614181 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,7 +52,8 @@ 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; +import org.jivesoftware.smackx.xdata.form.FillableForm; +import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.Jid; @@ -462,7 +463,8 @@ public final class AdHocCommandManager extends Manager { if (Action.next.equals(action)) { command.incrementStage(); - command.next(new Form(requestData.getForm())); + DataForm dataForm = requestData.getForm(); + command.next(new FillableForm(dataForm)); if (command.isLastStage()) { // If it is the last stage then the command is // completed @@ -475,7 +477,8 @@ public final class AdHocCommandManager extends Manager { } else if (Action.complete.equals(action)) { command.incrementStage(); - command.complete(new Form(requestData.getForm())); + DataForm dataForm = requestData.getForm(); + command.complete(new FillableForm(dataForm)); 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 f31e2a317..58d8cc74e 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,7 +23,8 @@ 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; +import org.jivesoftware.smackx.xdata.form.FillableForm; +import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.Jid; @@ -80,8 +81,8 @@ public class RemoteCommand extends AdHocCommand { } @Override - public void complete(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - executeAction(Action.complete, form); + public void complete(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + executeAction(Action.complete, form.getDataFormToSubmit()); } @Override @@ -100,13 +101,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(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - executeAction(Action.execute, form); + public void execute(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + executeAction(Action.execute, form.getDataFormToSubmit()); } @Override - public void next(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - executeAction(Action.next, form); + public void next(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + executeAction(Action.next, form.getDataFormToSubmit()); } @Override @@ -130,7 +131,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, Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + private void executeAction(Action action, DataForm 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. @@ -140,10 +141,7 @@ public class RemoteCommand extends AdHocCommand { data.setNode(getNode()); data.setSessionID(sessionID); data.setAction(action); - - if (form != null) { - data.setForm(form.getDataFormToSend()); - } + data.setForm(form); AdHocCommandData responseData = null; try { 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 e1575df8d..9e670acb3 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,6 +42,7 @@ 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; @@ -189,7 +190,7 @@ public final class FileTransferNegotiator extends Manager { public StreamNegotiator selectStreamNegotiator( FileTransferRequest request) throws NotConnectedException, NoStreamMethodsOfferedException, NoAcceptableTransferMechanisms, InterruptedException { StreamInitiation si = request.getStreamInitiation(); - FormField streamMethodField = getStreamMethodField(si + ListSingleFormField streamMethodField = getStreamMethodField(si .getFeatureNegotiationForm()); if (streamMethodField == null) { @@ -216,11 +217,11 @@ public final class FileTransferNegotiator extends Manager { return selectedStreamNegotiator; } - private static FormField getStreamMethodField(DataForm form) { - return form.getField(STREAM_DATA_FIELD_NAME); + private static ListSingleFormField getStreamMethodField(DataForm form) { + return (ListSingleFormField) form.getField(STREAM_DATA_FIELD_NAME); } - private StreamNegotiator getNegotiator(final FormField field) + private StreamNegotiator getNegotiator(final ListSingleFormField field) throws NoAcceptableTransferMechanisms { String variable; boolean isByteStream = false; @@ -359,16 +360,15 @@ public final class FileTransferNegotiator extends Manager { } private static DataForm createDefaultInitiationForm() { - DataForm form = new DataForm(DataForm.Type.form); - FormField.Builder fieldBuilder = FormField.builder(); - fieldBuilder.setFieldName(STREAM_DATA_FIELD_NAME) - .setType(FormField.Type.list_single); + DataForm.Builder form = DataForm.builder() + .setType(DataForm.Type.form); + ListSingleFormField.Builder fieldBuilder = FormField.listSingleBuilder(STREAM_DATA_FIELD_NAME); if (!IBB_ONLY) { fieldBuilder.addOption(Bytestream.NAMESPACE); } fieldBuilder.addOption(DataPacketExtension.NAMESPACE); form.addField(fieldBuilder.build()); - return form; + return form.build(); } } 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 2aaf4c73b..e41d2a38d 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,6 +33,7 @@ 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; @@ -80,13 +81,13 @@ public abstract class StreamNegotiator extends Manager { response.setType(IQ.Type.result); response.setStanzaId(streamInitiationOffer.getStanzaId()); - DataForm form = new DataForm(DataForm.Type.submit); - FormField.Builder field = FormField.builder( + DataForm.Builder form = DataForm.builder(); + ListSingleFormField.Builder field = FormField.listSingleBuilder( FileTransferNegotiator.STREAM_DATA_FIELD_NAME); - field.addValue(namespace); + field.setValue(namespace); form.addField(field.build()); - response.setFeatureNegotiationForm(form); + response.setFeatureNegotiationForm(form.build()); return response; } 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 new file mode 100644 index 000000000..4941d236d --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java @@ -0,0 +1,158 @@ +/** + * + * 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 new file mode 100644 index 000000000..4e6b2519a --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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/MucConfigFormManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java index 5a7adcc64..8d941ee06 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 Florian Schmaus + * Copyright 2015-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. @@ -23,17 +23,18 @@ 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 Form} used to + * Multi-User Chat configuration form manager is used to fill out and submit a {@link FilledForm} used to * configure rooms. *

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

*

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

*/ 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 @@ -73,7 +79,7 @@ public class MucConfigFormManager { public static final String MUC_ROOMCONFIG_ROOMSECRET = "muc#roomconfig_roomsecret"; private final MultiUserChat multiUserChat; - private final Form answerForm; + private final FillableForm answerForm; private final List owners; /** @@ -94,20 +100,13 @@ public class MucConfigFormManager { // Set the answer form Form configForm = multiUserChat.getConfigurationForm(); - 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()); - } + this.answerForm = configForm.getFillableForm(); // Set the local variables according to the fields found in the answer form - if (answerForm.hasField(MUC_ROOMCONFIG_ROOMOWNERS)) { + FormField roomOwnersFormField = answerForm.getDataForm().getField(MUC_ROOMCONFIG_ROOMOWNERS); + if (roomOwnersFormField != null) { // Set 'owners' to the currently configured owners - List ownerStrings = answerForm.getField(MUC_ROOMCONFIG_ROOMOWNERS).getValues(); + List ownerStrings = roomOwnersFormField.getValues(); owners = new ArrayList<>(ownerStrings.size()); JidUtil.jidsFrom(ownerStrings, owners, null); } @@ -244,7 +243,7 @@ public class MucConfigFormManager { } /** - * Submit the configuration as {@link Form} to the room. + * Submit the configuration as {@link FilledForm} 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/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index 0a2ce3060..422320bf1 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. + * Copyright 2003-2007 Jive Software. 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. @@ -75,8 +75,10 @@ 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; @@ -533,8 +535,8 @@ public class MultiUserChat { * instant room, use {@link #makeInstant()}. *

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

*/ public class MucCreateConfigFormHandle { @@ -552,7 +554,7 @@ public class MultiUserChat { */ public void makeInstant() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - sendConfigurationForm(new Form(DataForm.Type.submit)); + sendConfigurationForm(null); } /** @@ -804,7 +806,8 @@ public class MultiUserChat { iq.setType(IQ.Type.get); IQ answer = connection.createStanzaCollectorAndSend(iq).nextResultOrThrow(); - return Form.getFormFrom(answer); + DataForm dataForm = DataForm.from(answer, MucConfigFormManager.FORM_TYPE); + return new Form(dataForm); } /** @@ -817,11 +820,19 @@ public class MultiUserChat { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public void sendConfigurationForm(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + 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(); + } + MUCOwner iq = new MUCOwner(); iq.setTo(room); iq.setType(IQ.Type.set); - iq.addExtension(form.getDataFormToSend()); + iq.addExtension(dataForm); connection.createStanzaCollectorAndSend(iq).nextResultOrThrow(); } @@ -849,7 +860,8 @@ public class MultiUserChat { reg.setTo(room); IQ result = connection.createStanzaCollectorAndSend(reg).nextResultOrThrow(); - return Form.getFormFrom(result); + DataForm dataForm = DataForm.from(result); + return new Form(dataForm); } /** @@ -869,11 +881,11 @@ public class MultiUserChat { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public void sendRegistrationForm(Form form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public void sendRegistrationForm(FillableForm form) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { Registration reg = new Registration(); reg.setType(IQ.Type.set); reg.setTo(room); - reg.addExtension(form.getDataFormToSend()); + reg.addExtension(form.getDataFormToSubmit()); connection.createStanzaCollectorAndSend(reg).nextResultOrThrow(); } @@ -1247,19 +1259,17 @@ public class MultiUserChat { * @since 4.1 */ public void requestVoice() throws NotConnectedException, InterruptedException { - 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); + DataForm.Builder form = DataForm.builder() + .setFormType(MUCInitialPresence.NAMESPACE + "#request"); + + TextSingleFormField.Builder requestVoiceField = FormField.textSingleBuilder("muc#role"); requestVoiceField.setLabel("Requested role"); - requestVoiceField.addValue("participant"); + requestVoiceField.setValue("participant"); form.addField(requestVoiceField.build()); Message message = connection.getStanzaFactory().buildMessageStanza() .to(room) - .addExtension(form) + .addExtension(form.build()) .build(); connection.sendStanza(message); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatConstants.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatConstants.java new file mode 100644 index 000000000..ffc00a5a9 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatConstants.java @@ -0,0 +1,23 @@ +/** + * + * 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.muc; + +public class MultiUserChatConstants { + + public static final String NAMESPACE = "http://jabber.org/protocol/muc"; + +} 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 5c24b578e..d321903dd 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 Form form; + private final DataForm 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 = Form.getFormFrom(info); + form = DataForm.from(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 Form getForm() { + public DataForm getForm() { return form; } 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 4756c890d..8021054da 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. + * Copyright 2003-2007 Jive Software, 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. @@ -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.Form; +import org.jivesoftware.smackx.xdata.packet.DataForm; /** * 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); - Form extendedInfo = Form.getFormFrom(info); - if (extendedInfo != null) { - String value = extendedInfo.getField("number_of_messages").getFirstValue(); - return Integer.parseInt(value); + DataForm dataForm = DataForm.from(info, namespace); + if (dataForm == null) { + return 0; } - return 0; + String numberOfMessagesString = dataForm.getField("number_of_messages").getFirstValue(); + return Integer.parseInt(numberOfMessagesString); } /** 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 db847372d..7f47847b5 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,6 +22,8 @@ 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 @@ -30,18 +32,18 @@ import org.jivesoftware.smack.packet.ExtensionElement; * @author Robin Collier */ public class ConfigurationEvent extends NodeExtension implements EmbeddedPacketExtension { - private ConfigureForm form; + private final FilledConfigureForm form; public ConfigurationEvent(String nodeId) { - super(PubSubElementType.CONFIGURATION, nodeId); + this(nodeId, null); } - public ConfigurationEvent(String nodeId, ConfigureForm configForm) { + public ConfigurationEvent(String nodeId, FilledConfigureForm configForm) { super(PubSubElementType.CONFIGURATION, nodeId); form = configForm; } - public ConfigureForm getConfiguration() { + public FilledConfigureForm getConfiguration() { return form; } @@ -50,6 +52,6 @@ public class ConfigurationEvent extends NodeExtension implements EmbeddedPacketE if (getConfiguration() == null) return Collections.emptyList(); else - return Arrays.asList((ExtensionElement) getConfiguration().getDataFormToSend()); + return Arrays.asList((ExtensionElement) getConfiguration().getDataForm()); } } 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 deleted file mode 100644 index 01787ea03..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureForm.java +++ /dev/null @@ -1,643 +0,0 @@ -/** - * - * 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())); - } - - /** - * 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 30ef27c25..7ddf24468 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,12 +18,13 @@ package org.jivesoftware.smackx.pubsub; import java.net.URL; -import org.jivesoftware.smackx.xdata.Form; +import org.jivesoftware.smackx.pubsub.form.ConfigureForm; +import org.jivesoftware.smackx.xdata.form.FilledForm; /** * 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 Form} for configuration. + * for generic UI's using only a {@link FilledForm} for configuration. * * @author Robin Collier */ 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 9562dd4a0..6c3c69171 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.Form; +import org.jivesoftware.smackx.xdata.packet.DataForm; /** * Generic stanza extension which represents any PubSub form that is @@ -27,7 +27,7 @@ import org.jivesoftware.smackx.xdata.Form; * @author Robin Collier */ public class FormNode extends NodeExtension { - private final Form configForm; + private final DataForm 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, Form submitForm) { + public FormNode(FormNodeType formType, DataForm 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, Form submitForm) { + public FormNode(FormNodeType formType, String nodeId, DataForm submitForm) { super(formType.getNodeElement(), nodeId); if (submitForm == null) @@ -64,7 +64,7 @@ public class FormNode extends NodeExtension { * * @return The form */ - public Form getForm() { + public DataForm getForm() { return configForm; } @@ -84,7 +84,7 @@ public class FormNode extends NodeExtension { } else builder.append('>'); - builder.append(configForm.getDataFormToSend().toXML()); + builder.append(configForm.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 eaee0b168..727de6c48 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,6 +19,7 @@ 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 67051349d..eb93ec2d9 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,8 +16,10 @@ */ package org.jivesoftware.smackx.pubsub; +import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm; + /** - * These are the options for the node configuration setting {@link ConfigureForm#setItemReply(ItemReply)}, + * These are the options for the node configuration setting {@link FillableConfigureForm#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/LeafNode.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/LeafNode.java index a6eb39650..e604a47d4 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,6 +27,7 @@ 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 88decd520..313d0b93c 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,6 +37,10 @@ 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; @@ -45,7 +49,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.Form; +import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; @@ -81,7 +85,7 @@ public abstract class Node { } /** * Returns a configuration form, from which you can create an answer form to be submitted - * via the {@link #sendConfigurationForm(Form)}. + * via the {@link #sendConfigurationForm(FillableConfigureForm)}. * * @return the configuration form * @throws XMPPErrorException if there was an XMPP error returned. @@ -97,17 +101,17 @@ public abstract class Node { } /** - * Update the configuration with the contents of the new {@link Form}. + * Update the configuration with the contents of the new {@link FillableConfigureForm}. * - * @param submitForm TODO javadoc me please + * @param configureForm the filled node configuration form with the nodes new configuration. * @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(Form submitForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public void sendConfigurationForm(FillableConfigureForm configureForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { PubSub packet = createPubsubPacket(Type.set, new FormNode(FormNodeType.CONFIGURE_OWNER, - getId(), submitForm)); + getId(), configureForm.getDataFormToSubmit())); pubSubManager.getConnection().createStanzaCollectorAndSend(packet).nextResultOrThrow(); } @@ -454,9 +458,10 @@ 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, SubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public Subscription subscribe(Jid jid, FillableSubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + DataForm submitForm = subForm.getDataFormToSubmit(); PubSub request = createPubsubPacket(Type.set, new SubscribeExtension(jid, getId())); - request.addExtension(new FormNode(FormNodeType.OPTIONS, subForm)); + request.addExtension(new FormNode(FormNodeType.OPTIONS, submitForm)); PubSub reply = sendPubsubPacket(request); return reply.getExtension(PubSubElementType.SUBSCRIPTION); } @@ -483,11 +488,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, SubscribeForm)} instead. + * @deprecated use {@link #subscribe(Jid, FillableSubscribeForm)} instead. */ @Deprecated // TODO: Remove in Smack 4.5. - public Subscription subscribe(String jidString, SubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public Subscription subscribe(String jidString, FillableSubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { Jid jid; try { jid = JidCreate.from(jidString); @@ -529,7 +534,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(Form)}. + * via the {@link #sendConfigurationForm(FillableConfigureForm)}. * * @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 812f56b21..d915b2bc6 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,9 +16,11 @@ */ package org.jivesoftware.smackx.pubsub; +import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm; + /** * Specify the delivery style for event notifications. Denotes possible values - * for {@link ConfigureForm#setNotificationType(NotificationType)}. + * for {@link FillableConfigureForm#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 f2c9058aa..f22a8e876 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,6 +20,7 @@ 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 e4fee0d65..7fbe9e094 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,6 +16,8 @@ */ 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 1b9b28a55..f328bde81 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,11 +46,12 @@ 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.Form; -import org.jivesoftware.smackx.xdata.FormField; +import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.DomainBareJid; @@ -255,16 +256,18 @@ 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, Form config) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public Node createNode(String nodeId, FillableConfigureForm config) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { PubSub request = PubSub.createPubsubPacket(pubSubService, Type.set, new NodeExtension(PubSubElementType.CREATE, nodeId)); boolean isLeafNode = true; if (config != null) { - 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()); + 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; } // 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 f92ff24b3..321866cbc 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,9 +16,11 @@ */ package org.jivesoftware.smackx.pubsub; +import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm; + /** * Determines who may publish to a node. Denotes possible values - * for {@link ConfigureForm#setPublishModel(PublishModel)}. + * for {@link FillableConfigureForm#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 deleted file mode 100644 index cdcaffaf5..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java +++ /dev/null @@ -1,214 +0,0 @@ -/** - * - * 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/form/ConfigureForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/ConfigureForm.java new file mode 100644 index 000000000..f6a4c78b3 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/ConfigureForm.java @@ -0,0 +1,33 @@ +/** + * + * 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 new file mode 100644 index 000000000..0184c41d8 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/ConfigureFormReader.java @@ -0,0 +1,292 @@ +/** + * + * 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 new file mode 100644 index 000000000..0d7ebd039 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FillableConfigureForm.java @@ -0,0 +1,314 @@ +/** + * + * 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 new file mode 100644 index 000000000..396678097 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FillableSubscribeForm.java @@ -0,0 +1,95 @@ +/** + * + * 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 new file mode 100644 index 000000000..74d2e48f1 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FilledConfigureForm.java @@ -0,0 +1,29 @@ +/** + * + * 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 new file mode 100644 index 000000000..67a82ad3a --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/FilledSubscribeForm.java @@ -0,0 +1,29 @@ +/** + * + * 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 new file mode 100644 index 000000000..b1e173d5d --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/SubscribeForm.java @@ -0,0 +1,29 @@ +/** + * + * 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 new file mode 100644 index 000000000..f6a691e58 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/SubscribeFormReader.java @@ -0,0 +1,95 @@ +/** + * + * 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 new file mode 100644 index 000000000..4a119d341 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/form/package-info.java @@ -0,0 +1,21 @@ +/** + * + * 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 f13bd1621..1ae3324f8 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.ConfigureForm; +import org.jivesoftware.smackx.pubsub.form.FilledConfigureForm; 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"), new Form((DataForm) content.iterator().next())); + return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), (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 9d386e609..d89cfee63 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.xdata.Form; +import org.jivesoftware.smackx.pubsub.form.ConfigureForm; +import org.jivesoftware.smackx.xdata.packet.DataForm; /** * 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()); - Form formReply = config.getForm(); - return new ConfigureForm(formReply); + DataForm dataForm = config.getForm(); + return new ConfigureForm(dataForm); } } 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 80c701e48..36853164b 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.getVariable(), field.getType())); + columns.add(new Column(field.getLabel(), field.getFieldName(), 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.getVariable(), values)); + fieldList.add(new Field(field.getFieldName(), 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 702e494ea..ac7f36901 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 Form form; + private DataForm form; private ReportedData data; SimpleUserSearch() { super(ELEMENT, NAMESPACE); } - public void setForm(Form form) { + public void setForm(DataForm form) { this.form = form; } @@ -65,7 +65,7 @@ class SimpleUserSearch extends IQ { StringBuilder buf = new StringBuilder(); if (form == null) { - form = Form.getFormFrom(this); + form = DataForm.from(this); } if (form == null) { @@ -73,7 +73,7 @@ class SimpleUserSearch extends IQ { } for (FormField field : form.getFields()) { - String name = field.getVariable(); + String name = field.getFieldName(); 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 16167b293..bac591b78 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,8 +31,6 @@ 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; @@ -70,13 +68,13 @@ public class UserSearch extends SimpleIQ { * @throws NotConnectedException if the XMPP connection is not connected. * @throws InterruptedException if the calling thread was interrupted. */ - public Form getSearchForm(XMPPConnection con, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public DataForm 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 Form.getFormFrom(response); + return DataForm.from(response, NAMESPACE); } /** @@ -91,11 +89,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, Form searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public ReportedData sendSearchForm(XMPPConnection con, DataForm searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { UserSearch search = new UserSearch(); search.setType(IQ.Type.set); search.setTo(searchService); - search.addExtension(searchForm.getDataFormToSend()); + search.addExtension(searchForm); IQ response = con.createStanzaCollectorAndSend(search).nextResultOrThrow(); return ReportedData.getReportedDataFrom(response); @@ -113,7 +111,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, Form searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public ReportedData sendSimpleSearchForm(XMPPConnection con, DataForm searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { SimpleUserSearch search = new SimpleUserSearch(); search.setForm(searchForm); search.setType(IQ.Type.set); @@ -137,11 +135,7 @@ public class UserSearch extends SimpleIQ { boolean done = false; while (!done) { XmlPullParser.Event eventType = parser.next(); - 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")) { + if (eventType == XmlPullParser.Event.START_ELEMENT && parser.getName().equals("item")) { simpleUserSearch.parseItems(parser); return simpleUserSearch; } @@ -164,50 +158,4 @@ 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 85bcddd6f..866d66721 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.Form; +import org.jivesoftware.smackx.xdata.packet.DataForm; 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 Form getSearchForm(DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public DataForm 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(Form searchForm, DomainBareJid searchService) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + public ReportedData getSearchResults(DataForm 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/xdata/AbstractMultiFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java new file mode 100644 index 000000000..31c778a87 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java @@ -0,0 +1,92 @@ +/** + * + * 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 new file mode 100644 index 000000000..27fca4cde --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java @@ -0,0 +1,100 @@ +/** + * + * 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 new file mode 100644 index 000000000..2b036b162 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java @@ -0,0 +1,98 @@ +/** + * + * 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 deleted file mode 100644 index 9e5bb999a..000000000 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/Form.java +++ /dev/null @@ -1,517 +0,0 @@ -/** - * - * 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 525d8fa75..4bc28a7ef 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 Florian Schmaus. + * Copyright 2003-2007 Jive Software, 2019-2020 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ 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; @@ -43,10 +44,14 @@ 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 final class FormField implements FullyQualifiedElement { +public abstract class FormField implements FullyQualifiedElement { public static final String ELEMENT = "field"; @@ -154,7 +159,7 @@ public final class FormField implements FullyQualifiedElement { /** * The field's name. Put as value in the 'var' attribute of <field/>. */ - private final String variable; + private final String fieldName; private final String label; @@ -168,8 +173,6 @@ public final 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