DataFormProvider.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software 2020-2022 Florian Schmaus.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.jivesoftware.smackx.xdata.provider;

  18. import java.io.IOException;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.List;
  22. import java.util.Set;
  23. import java.util.concurrent.CopyOnWriteArraySet;
  24. import java.util.logging.Logger;

  25. import javax.xml.namespace.QName;

  26. import org.jivesoftware.smack.packet.XmlEnvironment;
  27. import org.jivesoftware.smack.parsing.SmackParsingException;
  28. import org.jivesoftware.smack.provider.ExtensionElementProvider;
  29. import org.jivesoftware.smack.roster.packet.RosterPacket;
  30. import org.jivesoftware.smack.roster.provider.RosterPacketProvider;
  31. import org.jivesoftware.smack.util.EqualsUtil;
  32. import org.jivesoftware.smack.util.HashCode;
  33. import org.jivesoftware.smack.xml.XmlPullParser;
  34. import org.jivesoftware.smack.xml.XmlPullParserException;

  35. import org.jivesoftware.smackx.formtypes.FormFieldRegistry;
  36. import org.jivesoftware.smackx.xdata.AbstractMultiFormField;
  37. import org.jivesoftware.smackx.xdata.AbstractSingleStringValueFormField;
  38. import org.jivesoftware.smackx.xdata.BooleanFormField;
  39. import org.jivesoftware.smackx.xdata.FormField;
  40. import org.jivesoftware.smackx.xdata.FormFieldChildElement;
  41. import org.jivesoftware.smackx.xdata.FormFieldWithOptions;
  42. import org.jivesoftware.smackx.xdata.JidMultiFormField;
  43. import org.jivesoftware.smackx.xdata.JidSingleFormField;
  44. import org.jivesoftware.smackx.xdata.ListMultiFormField;
  45. import org.jivesoftware.smackx.xdata.ListSingleFormField;
  46. import org.jivesoftware.smackx.xdata.TextSingleFormField;
  47. import org.jivesoftware.smackx.xdata.packet.DataForm;
  48. import org.jivesoftware.smackx.xdatalayout.packet.DataLayout;
  49. import org.jivesoftware.smackx.xdatalayout.provider.DataLayoutProvider;

  50. /**
  51.  * The DataFormProvider parses DataForm packets.
  52.  *
  53.  * @author Gaston Dombiak
  54.  */
  55. public class DataFormProvider extends ExtensionElementProvider<DataForm> {

  56.     private static final Logger LOGGER = Logger.getLogger(DataFormProvider.class.getName());

  57.     public static final DataFormProvider INSTANCE = new DataFormProvider();

  58.     @Override
  59.     public DataForm parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
  60.         DataForm.Type dataFormType = DataForm.Type.fromString(parser.getAttributeValue("", "type"));
  61.         DataForm.Builder dataForm = DataForm.builder(dataFormType);

  62.         String formType = null;
  63.         DataForm.ReportedData reportedData = null;

  64.         outerloop: while (true) {
  65.             XmlPullParser.Event eventType = parser.next();
  66.             switch (eventType) {
  67.             case START_ELEMENT:
  68.                 String name = parser.getName();
  69.                 String namespace = parser.getNamespace();
  70.                 XmlEnvironment elementXmlEnvironment = XmlEnvironment.from(parser, xmlEnvironment);
  71.                 switch (name) {
  72.                 case "instructions":
  73.                     dataForm.addInstruction(parser.nextText());
  74.                     break;
  75.                 case "title":
  76.                     dataForm.setTitle(parser.nextText());
  77.                     break;
  78.                 case "field":
  79.                     // Note that we parse this form field without any potential reportedData. We only use reportedData
  80.                     // to lookup form field types of fields under <item/>.
  81.                     FormField formField = parseField(parser, elementXmlEnvironment, formType);

  82.                     TextSingleFormField hiddenFormTypeField = formField.asHiddenFormTypeFieldIfPossible();
  83.                     if (hiddenFormTypeField != null) {
  84.                         if (formType != null) {
  85.                             throw new SmackParsingException("Multiple hidden form type fields");
  86.                         }
  87.                         formType = hiddenFormTypeField.getValue();
  88.                     }

  89.                     dataForm.addField(formField);
  90.                     break;
  91.                 case "item":
  92.                     DataForm.Item item = parseItem(parser, elementXmlEnvironment, formType, reportedData);
  93.                     dataForm.addItem(item);
  94.                     break;
  95.                 case "reported":
  96.                     if (reportedData != null) {
  97.                         throw new SmackParsingException("Data form with multiple <reported/> elements");
  98.                     }
  99.                     reportedData = parseReported(parser, elementXmlEnvironment, formType);
  100.                     dataForm.setReportedData(reportedData);
  101.                     break;
  102.                 // See XEP-133 Example 32 for a corner case where the data form contains this extension.
  103.                 case RosterPacket.ELEMENT:
  104.                     if (namespace.equals(RosterPacket.NAMESPACE)) {
  105.                         dataForm.addExtensionElement(RosterPacketProvider.INSTANCE.parse(parser, null));
  106.                     }
  107.                     break;
  108.                 // See XEP-141 Data Forms Layout
  109.                 case DataLayout.ELEMENT:
  110.                     if (namespace.equals(DataLayout.NAMESPACE)) {
  111.                         dataForm.addExtensionElement(DataLayoutProvider.parse(parser));
  112.                     }
  113.                     break;
  114.                 }
  115.                 break;
  116.             case END_ELEMENT:
  117.                 if (parser.getDepth() == initialDepth) {
  118.                     break outerloop;
  119.                 }
  120.                 break;
  121.             default:
  122.                 // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
  123.                 break;
  124.             }
  125.         }
  126.         return dataForm.build();
  127.     }

  128.     private static FormField parseField(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType)
  129.                     throws XmlPullParserException, IOException, SmackParsingException {
  130.         return parseField(parser, xmlEnvironment, formType, null);
  131.     }

  132.     private static final class FieldNameAndFormType {
  133.         private final String fieldName;
  134.         private final String formType;

  135.         private FieldNameAndFormType(String fieldName, String formType) {
  136.             this.fieldName = fieldName;
  137.             this.formType = formType;
  138.         }

  139.         private final HashCode.Cache hashCodeCache = new HashCode.Cache();

  140.         @Override
  141.         public int hashCode() {
  142.             return hashCodeCache.getHashCode(b ->
  143.                            b.append(fieldName)
  144.                             .append(formType)
  145.                             .build()
  146.             );
  147.         }

  148.         @Override
  149.         public boolean equals(Object other) {
  150.             return EqualsUtil.equals(this, other, (e, o) ->
  151.                 e.append(fieldName, o.fieldName)
  152.                  .append(formType, o.formType)
  153.             );
  154.         }
  155.     }

  156.     private static final Set<FieldNameAndFormType> UNKNOWN_FIELDS = new CopyOnWriteArraySet<>();

  157.     private static FormField parseField(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType, DataForm.ReportedData reportedData)
  158.                     throws XmlPullParserException, IOException, SmackParsingException {
  159.         final int initialDepth = parser.getDepth();

  160.         final String fieldName = parser.getAttributeValue("var");
  161.         final String label = parser.getAttributeValue("", "label");

  162.         FormField.Type type = null;
  163.         {
  164.             String fieldTypeString = parser.getAttributeValue("type");
  165.             if (fieldTypeString != null) {
  166.                 type = FormField.Type.fromString(fieldTypeString);
  167.             }
  168.         }

  169.         List<FormField.Value> values = new ArrayList<>();
  170.         List<FormField.Option> options = new ArrayList<>();
  171.         List<FormFieldChildElement> childElements = new ArrayList<>();
  172.         boolean required = false;

  173.         outerloop: while (true) {
  174.             XmlPullParser.TagEvent eventType = parser.nextTag();
  175.             switch (eventType) {
  176.             case START_ELEMENT:
  177.                 QName qname = parser.getQName();
  178.                 if (qname.equals(FormField.Value.QNAME)) {
  179.                     FormField.Value value = parseValue(parser);
  180.                     values.add(value);
  181.                 } else if (qname.equals(FormField.Option.QNAME)) {
  182.                     FormField.Option option = parseOption(parser);
  183.                     options.add(option);
  184.                 } else if (qname.equals(FormField.Required.QNAME)) {
  185.                     required = true;
  186.                 } else {
  187.                     FormFieldChildElementProvider<?> provider = FormFieldChildElementProviderManager.getFormFieldChildElementProvider(
  188.                                     qname);
  189.                     if (provider == null) {
  190.                         LOGGER.warning("Unknown form field child element " + qname + " ignored");
  191.                         continue;
  192.                     }
  193.                     FormFieldChildElement formFieldChildElement = provider.parse(parser,
  194.                                     XmlEnvironment.from(parser, xmlEnvironment));
  195.                     childElements.add(formFieldChildElement);
  196.                 }
  197.                 break;
  198.             case END_ELEMENT:
  199.                 if (parser.getDepth() == initialDepth) {
  200.                     break outerloop;
  201.                 }
  202.                 break;
  203.             }
  204.         }

  205.         // Data forms of type 'result' may contain <reported/> and <item/> elements. If this is the case, then the type
  206.         // of the <field/>s within the <item/> elements is determined by the information found in <reported/>. See
  207.         // XEP-0004 § 3.4 and SMACK-902
  208.         if (type == null && reportedData != null) {
  209.             FormField reportedFormField = reportedData.getField(fieldName);
  210.             if (reportedFormField != null) {
  211.                 type = reportedFormField.getType();
  212.             }
  213.         }

  214.         if (type == null) {
  215.             // The field name 'FORM_TYPE' is magic.
  216.             if (fieldName.equals(FormField.FORM_TYPE)) {
  217.                 type = FormField.Type.hidden;
  218.             } else {
  219.                 // If no field type was explicitly provided, then we need to lookup the
  220.                 // field's type in the registry.
  221.                 type = FormFieldRegistry.lookup(formType, fieldName);
  222.                 if (type == null) {
  223.                     FieldNameAndFormType fieldNameAndFormType = new FieldNameAndFormType(fieldName, formType);
  224.                     if (!UNKNOWN_FIELDS.contains(fieldNameAndFormType)) {
  225.                         LOGGER.warning("The Field '" + fieldName + "' from FORM_TYPE '" + formType
  226.                                         + "' is not registered. Field type is unknown, assuming text-single.");
  227.                         UNKNOWN_FIELDS.add(fieldNameAndFormType);
  228.                     }
  229.                     // As per XEP-0004, text-single is the default form field type, which we use as emergency fallback here.
  230.                     type = FormField.Type.text_single;
  231.                 }
  232.             }
  233.         }

  234.         FormField.Builder<?, ?> builder;
  235.         switch (type) {
  236.         case bool:
  237.             builder = parseBooleanFormField(fieldName, values);
  238.             break;
  239.         case fixed:
  240.             builder = parseSingleKindFormField(FormField.fixedBuilder(fieldName), values);
  241.             break;
  242.         case hidden:
  243.             builder = parseSingleKindFormField(FormField.hiddenBuilder(fieldName), values);
  244.             break;
  245.         case jid_multi:
  246.             JidMultiFormField.Builder jidMultiBuilder = FormField.jidMultiBuilder(fieldName);
  247.             for (FormField.Value value : values) {
  248.                 jidMultiBuilder.addValue(value);
  249.             }
  250.             builder = jidMultiBuilder;
  251.             break;
  252.         case jid_single:
  253.             ensureAtMostSingleValue(type, values);
  254.             JidSingleFormField.Builder jidSingleBuilder = FormField.jidSingleBuilder(fieldName);
  255.             if (!values.isEmpty()) {
  256.                 FormField.Value value = values.get(0);
  257.                 jidSingleBuilder.setValue(value);
  258.             }
  259.             builder = jidSingleBuilder;
  260.             break;
  261.         case list_multi:
  262.             ListMultiFormField.Builder listMultiBuilder = FormField.listMultiBuilder(fieldName);
  263.             addOptionsToBuilder(options, listMultiBuilder);
  264.             builder = parseMultiKindFormField(listMultiBuilder, values);
  265.             break;
  266.         case list_single:
  267.             ListSingleFormField.Builder listSingleBuilder = FormField.listSingleBuilder(fieldName);
  268.             addOptionsToBuilder(options, listSingleBuilder);
  269.             builder = parseSingleKindFormField(listSingleBuilder, values);
  270.             break;
  271.         case text_multi:
  272.             builder = parseMultiKindFormField(FormField.textMultiBuilder(fieldName), values);
  273.             break;
  274.         case text_private:
  275.             builder = parseSingleKindFormField(FormField.textPrivateBuilder(fieldName), values);
  276.             break;
  277.         case text_single:
  278.             builder = parseSingleKindFormField(FormField.textSingleBuilder(fieldName), values);
  279.             break;
  280.         default:
  281.             // Should never happen, as we cover all types in the switch/case.
  282.             throw new AssertionError("Unknown type " + type);
  283.         }


  284.         switch (type) {
  285.         case list_multi:
  286.         case list_single:
  287.             break;
  288.         default:
  289.             if (!options.isEmpty()) {
  290.                 throw new SmackParsingException("Form fields of type " + type + " must not have options. This one had "
  291.                                 + options.size());
  292.             }
  293.             break;
  294.         }

  295.         if (label != null) {
  296.             builder.setLabel(label);
  297.         }
  298.         builder.setRequired(required);
  299.         builder.addFormFieldChildElements(childElements);

  300.         return builder.build();
  301.     }

  302.     private static FormField.Builder<?, ?> parseBooleanFormField(String fieldName, List<FormField.Value> values) throws SmackParsingException {
  303.         BooleanFormField.Builder builder = FormField.booleanBuilder(fieldName);
  304.         ensureAtMostSingleValue(builder.getType(), values);
  305.         if (values.size() == 1) {
  306.             FormField.Value value = values.get(0);
  307.             builder.setValue(value);
  308.         }
  309.         return builder;
  310.     }

  311.     private static AbstractSingleStringValueFormField.Builder<?, ?> parseSingleKindFormField(
  312.                     AbstractSingleStringValueFormField.Builder<?, ?> builder, List<FormField.Value> values)
  313.                     throws SmackParsingException {
  314.         ensureAtMostSingleValue(builder.getType(), values);
  315.         if (values.size() == 1) {
  316.             String value = values.get(0).getValue().toString();
  317.             builder.setValue(value);
  318.         }
  319.         return builder;
  320.     }

  321.     private static AbstractMultiFormField.Builder<?, ?> parseMultiKindFormField(AbstractMultiFormField.Builder<?, ?> builder,
  322.                     List<FormField.Value> values) {
  323.         for (FormField.Value value : values) {
  324.             String rawValue = value.getValue().toString();
  325.             builder.addValue(rawValue);
  326.         }
  327.         return builder;
  328.     }

  329.     private static DataForm.Item parseItem(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType,
  330.                     DataForm.ReportedData reportedData)
  331.                     throws XmlPullParserException, IOException, SmackParsingException {
  332.         final int initialDepth = parser.getDepth();
  333.         List<FormField> fields = new ArrayList<>();
  334.         outerloop: while (true) {
  335.             XmlPullParser.TagEvent eventType = parser.nextTag();
  336.             switch (eventType) {
  337.             case START_ELEMENT:
  338.                 String name = parser.getName();
  339.                 switch (name) {
  340.                 case "field":
  341.                     FormField field = parseField(parser, XmlEnvironment.from(parser, xmlEnvironment), formType,
  342.                                     reportedData);
  343.                     fields.add(field);
  344.                     break;
  345.                 }
  346.                 break;
  347.             case END_ELEMENT:
  348.                 if (parser.getDepth() == initialDepth) {
  349.                     break outerloop;
  350.                 }
  351.                 break;
  352.             }
  353.         }
  354.         return new DataForm.Item(fields);
  355.     }

  356.     private static DataForm.ReportedData parseReported(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType)
  357.                     throws XmlPullParserException, IOException, SmackParsingException {
  358.         final int initialDepth = parser.getDepth();
  359.         List<FormField> fields = new ArrayList<>();
  360.         outerloop: while (true) {
  361.             XmlPullParser.TagEvent eventType = parser.nextTag();
  362.             switch (eventType) {
  363.             case START_ELEMENT:
  364.                 String name = parser.getName();
  365.                 switch (name) {
  366.                 case "field":
  367.                     FormField field = parseField(parser, XmlEnvironment.from(parser, xmlEnvironment), formType);
  368.                     fields.add(field);
  369.                     break;
  370.                 }
  371.                 break;
  372.             case END_ELEMENT:
  373.                 if (parser.getDepth() == initialDepth) {
  374.                     break outerloop;
  375.                 }
  376.                 break;
  377.             }
  378.         }
  379.         return new DataForm.ReportedData(fields);
  380.     }

  381.     public static FormField.Value parseValue(XmlPullParser parser) throws IOException, XmlPullParserException {
  382.         String value = parser.nextText();
  383.         return new FormField.Value(value);
  384.     }

  385.     public static FormField.Option parseOption(XmlPullParser parser) throws IOException, XmlPullParserException {
  386.         int initialDepth = parser.getDepth();
  387.         FormField.Option option = null;
  388.         String label = parser.getAttributeValue("", "label");
  389.         outerloop: while (true) {
  390.             XmlPullParser.TagEvent eventType = parser.nextTag();
  391.             switch (eventType) {
  392.             case START_ELEMENT:
  393.                 String name = parser.getName();
  394.                 switch (name) {
  395.                 case "value":
  396.                     option = new FormField.Option(label, parser.nextText());
  397.                     break;
  398.                 }
  399.                 break;
  400.             case END_ELEMENT:
  401.                 if (parser.getDepth() == initialDepth) {
  402.                     break outerloop;
  403.                 }
  404.                 break;
  405.             }
  406.         }
  407.         return option;
  408.     }

  409.     private static void ensureAtMostSingleValue(FormField.Type type, List<FormField.Value> values) throws SmackParsingException {
  410.         if (values.size() > 1) {
  411.             throw new SmackParsingException(type + " fields can have at most one value, this one had " + values.size());
  412.         }
  413.     }

  414.     private static void addOptionsToBuilder(Collection<FormField.Option> options, FormFieldWithOptions.Builder<?> builder) {
  415.         for (FormField.Option option : options) {
  416.             builder.addOption(option);
  417.         }
  418.     }
  419. }