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