001/** 002 * 003 * Copyright 2003-2007 Jive Software 2020 Florian Schmaus. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smackx.xdata.provider; 019 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.List; 024import java.util.logging.Logger; 025 026import javax.xml.namespace.QName; 027 028import org.jivesoftware.smack.packet.XmlEnvironment; 029import org.jivesoftware.smack.parsing.SmackParsingException; 030import org.jivesoftware.smack.provider.ExtensionElementProvider; 031import org.jivesoftware.smack.roster.packet.RosterPacket; 032import org.jivesoftware.smack.roster.provider.RosterPacketProvider; 033import org.jivesoftware.smack.xml.XmlPullParser; 034import org.jivesoftware.smack.xml.XmlPullParserException; 035 036import org.jivesoftware.smackx.formtypes.FormFieldRegistry; 037import org.jivesoftware.smackx.xdata.AbstractMultiFormField; 038import org.jivesoftware.smackx.xdata.AbstractSingleStringValueFormField; 039import org.jivesoftware.smackx.xdata.BooleanFormField; 040import org.jivesoftware.smackx.xdata.FormField; 041import org.jivesoftware.smackx.xdata.FormFieldChildElement; 042import org.jivesoftware.smackx.xdata.FormFieldWithOptions; 043import org.jivesoftware.smackx.xdata.JidMultiFormField; 044import org.jivesoftware.smackx.xdata.JidSingleFormField; 045import org.jivesoftware.smackx.xdata.ListMultiFormField; 046import org.jivesoftware.smackx.xdata.ListSingleFormField; 047import org.jivesoftware.smackx.xdata.TextSingleFormField; 048import org.jivesoftware.smackx.xdata.packet.DataForm; 049import org.jivesoftware.smackx.xdatalayout.packet.DataLayout; 050import org.jivesoftware.smackx.xdatalayout.provider.DataLayoutProvider; 051 052import org.jxmpp.jid.Jid; 053import org.jxmpp.jid.impl.JidCreate; 054 055/** 056 * The DataFormProvider parses DataForm packets. 057 * 058 * @author Gaston Dombiak 059 */ 060public class DataFormProvider extends ExtensionElementProvider<DataForm> { 061 062 private static final Logger LOGGER = Logger.getLogger(DataFormProvider.class.getName()); 063 064 public static final DataFormProvider INSTANCE = new DataFormProvider(); 065 066 @Override 067 public DataForm parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException { 068 DataForm.Type dataFormType = DataForm.Type.fromString(parser.getAttributeValue("", "type")); 069 DataForm.Builder dataForm = DataForm.builder(); 070 dataForm.setType(dataFormType); 071 072 String formType = null; 073 074 outerloop: while (true) { 075 XmlPullParser.Event eventType = parser.next(); 076 switch (eventType) { 077 case START_ELEMENT: 078 String name = parser.getName(); 079 String namespace = parser.getNamespace(); 080 XmlEnvironment elementXmlEnvironment = XmlEnvironment.from(parser, xmlEnvironment); 081 switch (name) { 082 case "instructions": 083 dataForm.addInstruction(parser.nextText()); 084 break; 085 case "title": 086 dataForm.setTitle(parser.nextText()); 087 break; 088 case "field": 089 FormField formField = parseField(parser, elementXmlEnvironment, formType); 090 091 TextSingleFormField hiddenFormTypeField = formField.asHiddenFormTypeFieldIfPossible(); 092 if (hiddenFormTypeField != null) { 093 if (formType != null) { 094 throw new SmackParsingException("Multiple hidden form type fields"); 095 } 096 formType = hiddenFormTypeField.getValue(); 097 } 098 099 dataForm.addField(formField); 100 break; 101 case "item": 102 DataForm.Item item = parseItem(parser, elementXmlEnvironment, formType); 103 dataForm.addItem(item); 104 break; 105 case "reported": 106 DataForm.ReportedData reported = parseReported(parser, elementXmlEnvironment, formType); 107 dataForm.setReportedData(reported); 108 break; 109 // See XEP-133 Example 32 for a corner case where the data form contains this extension. 110 case RosterPacket.ELEMENT: 111 if (namespace.equals(RosterPacket.NAMESPACE)) { 112 dataForm.addExtensionElement(RosterPacketProvider.INSTANCE.parse(parser)); 113 } 114 break; 115 // See XEP-141 Data Forms Layout 116 case DataLayout.ELEMENT: 117 if (namespace.equals(DataLayout.NAMESPACE)) { 118 dataForm.addExtensionElement(DataLayoutProvider.parse(parser)); 119 } 120 break; 121 } 122 break; 123 case END_ELEMENT: 124 if (parser.getDepth() == initialDepth) { 125 break outerloop; 126 } 127 break; 128 default: 129 // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement. 130 break; 131 } 132 } 133 return dataForm.build(); 134 } 135 136 private static FormField parseField(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType) 137 throws XmlPullParserException, IOException, SmackParsingException { 138 final int initialDepth = parser.getDepth(); 139 140 final String fieldName = parser.getAttributeValue("var"); 141 final String label = parser.getAttributeValue("", "label"); 142 143 FormField.Type type = null; 144 { 145 String fieldTypeString = parser.getAttributeValue("type"); 146 if (fieldTypeString != null) { 147 type = FormField.Type.fromString(fieldTypeString); 148 } 149 } 150 151 List<FormField.Value> values = new ArrayList<>(); 152 List<FormField.Option> options = new ArrayList<>(); 153 List<FormFieldChildElement> childElements = new ArrayList<>(); 154 boolean required = false; 155 156 outerloop: while (true) { 157 XmlPullParser.TagEvent eventType = parser.nextTag(); 158 switch (eventType) { 159 case START_ELEMENT: 160 QName qname = parser.getQName(); 161 if (qname.equals(FormField.Value.QNAME)) { 162 FormField.Value value = parseValue(parser); 163 values.add(value); 164 } else if (qname.equals(FormField.Option.QNAME)) { 165 FormField.Option option = parseOption(parser); 166 options.add(option); 167 } else if (qname.equals(FormField.Required.QNAME)) { 168 required = true; 169 } else { 170 FormFieldChildElementProvider<?> provider = FormFieldChildElementProviderManager.getFormFieldChildElementProvider( 171 qname); 172 if (provider == null) { 173 LOGGER.warning("Unknown form field child element " + qname + " ignored"); 174 continue; 175 } 176 FormFieldChildElement formFieldChildElement = provider.parse(parser, 177 XmlEnvironment.from(parser, xmlEnvironment)); 178 childElements.add(formFieldChildElement); 179 } 180 break; 181 case END_ELEMENT: 182 if (parser.getDepth() == initialDepth) { 183 break outerloop; 184 } 185 break; 186 } 187 } 188 189 if (type == null) { 190 // If no field type was explicitly provided, then we need to lookup the 191 // field's type in the registry. 192 type = FormFieldRegistry.lookup(formType, fieldName); 193 if (type == null) { 194 LOGGER.warning("The Field '" + fieldName + "' from FORM_TYPE '" + formType 195 + "' is not registered. Field type is unknown, assuming text-single."); 196 // As per XEP-0004, text-single is the default form field type, which we use as emergency fallback here. 197 type = FormField.Type.text_single; 198 } 199 } 200 201 FormField.Builder<?, ?> builder; 202 switch (type) { 203 case bool: 204 builder = parseBooleanFormField(fieldName, values); 205 break; 206 case fixed: 207 builder = parseSingleKindFormField(FormField.fixedBuilder(fieldName), values); 208 break; 209 case hidden: 210 builder = parseSingleKindFormField(FormField.hiddenBuilder(fieldName), values); 211 break; 212 case jid_multi: 213 JidMultiFormField.Builder jidMultiBuilder = FormField.jidMultiBuilder(fieldName); 214 for (FormField.Value value : values) { 215 Jid jid = JidCreate.from(value.getValue()); 216 jidMultiBuilder.addValue(jid); 217 } 218 builder = jidMultiBuilder; 219 break; 220 case jid_single: 221 ensureAtMostSingleValue(type, values); 222 JidSingleFormField.Builder jidSingleBuilder = FormField.jidSingleBuilder(fieldName); 223 if (!values.isEmpty()) { 224 CharSequence jidCharSequence = values.get(0).getValue(); 225 Jid jid = JidCreate.from(jidCharSequence); 226 jidSingleBuilder.setValue(jid); 227 } 228 builder = jidSingleBuilder; 229 break; 230 case list_multi: 231 ListMultiFormField.Builder listMultiBuilder = FormField.listMultiBuilder(fieldName); 232 addOptionsToBuilder(options, listMultiBuilder); 233 builder = parseMultiKindFormField(listMultiBuilder, values); 234 break; 235 case list_single: 236 ListSingleFormField.Builder listSingleBuilder = FormField.listSingleBuilder(fieldName); 237 addOptionsToBuilder(options, listSingleBuilder); 238 builder = parseSingleKindFormField(listSingleBuilder, values); 239 break; 240 case text_multi: 241 builder = parseMultiKindFormField(FormField.textMultiBuilder(fieldName), values); 242 break; 243 case text_private: 244 builder = parseSingleKindFormField(FormField.textPrivateBuilder(fieldName), values); 245 break; 246 case text_single: 247 builder = parseSingleKindFormField(FormField.textSingleBuilder(fieldName), values); 248 break; 249 default: 250 // Should never happen, as we cover all types in the switch/case. 251 throw new AssertionError("Unknown type " + type); 252 } 253 254 255 switch (type) { 256 case list_multi: 257 case list_single: 258 break; 259 default: 260 if (!options.isEmpty()) { 261 throw new SmackParsingException("Form fields of type " + type + " must not have options. This one had " 262 + options.size()); 263 } 264 break; 265 } 266 267 if (label != null) { 268 builder.setLabel(label); 269 } 270 builder.setRequired(required); 271 builder.addFormFieldChildElements(childElements); 272 273 return builder.build(); 274 } 275 276 private static FormField.Builder<?, ?> parseBooleanFormField(String fieldName, List<FormField.Value> values) throws SmackParsingException { 277 BooleanFormField.Builder builder = FormField.booleanBuilder(fieldName); 278 if (values.size() == 1) { 279 String value = values.get(0).getValue().toString(); 280 builder.setValue(value); 281 } 282 return builder; 283 } 284 285 private static AbstractSingleStringValueFormField.Builder<?, ?> parseSingleKindFormField( 286 AbstractSingleStringValueFormField.Builder<?, ?> builder, List<FormField.Value> values) 287 throws SmackParsingException { 288 ensureAtMostSingleValue(builder.getType(), values); 289 if (values.size() == 1) { 290 String value = values.get(0).getValue().toString(); 291 builder.setValue(value); 292 } 293 return builder; 294 } 295 296 private static AbstractMultiFormField.Builder<?, ?> parseMultiKindFormField(AbstractMultiFormField.Builder<?, ?> builder, 297 List<FormField.Value> values) { 298 for (FormField.Value value : values) { 299 builder.addValue(value.getValue()); 300 } 301 return builder; 302 } 303 304 private static DataForm.Item parseItem(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType) 305 throws XmlPullParserException, IOException, SmackParsingException { 306 final int initialDepth = parser.getDepth(); 307 List<FormField> fields = new ArrayList<>(); 308 outerloop: while (true) { 309 XmlPullParser.TagEvent eventType = parser.nextTag(); 310 switch (eventType) { 311 case START_ELEMENT: 312 String name = parser.getName(); 313 switch (name) { 314 case "field": 315 FormField field = parseField(parser, XmlEnvironment.from(parser, xmlEnvironment), formType); 316 fields.add(field); 317 break; 318 } 319 break; 320 case END_ELEMENT: 321 if (parser.getDepth() == initialDepth) { 322 break outerloop; 323 } 324 break; 325 } 326 } 327 return new DataForm.Item(fields); 328 } 329 330 private static DataForm.ReportedData parseReported(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType) 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 fields.add(field); 343 break; 344 } 345 break; 346 case END_ELEMENT: 347 if (parser.getDepth() == initialDepth) { 348 break outerloop; 349 } 350 break; 351 } 352 } 353 return new DataForm.ReportedData(fields); 354 } 355 356 public static FormField.Value parseValue(XmlPullParser parser) throws IOException, XmlPullParserException { 357 String value = parser.nextText(); 358 return new FormField.Value(value); 359 } 360 361 public static FormField.Option parseOption(XmlPullParser parser) throws IOException, XmlPullParserException { 362 int initialDepth = parser.getDepth(); 363 FormField.Option option = null; 364 String label = parser.getAttributeValue("", "label"); 365 outerloop: while (true) { 366 XmlPullParser.TagEvent eventType = parser.nextTag(); 367 switch (eventType) { 368 case START_ELEMENT: 369 String name = parser.getName(); 370 switch (name) { 371 case "value": 372 option = new FormField.Option(label, parser.nextText()); 373 break; 374 } 375 break; 376 case END_ELEMENT: 377 if (parser.getDepth() == initialDepth) { 378 break outerloop; 379 } 380 break; 381 } 382 } 383 return option; 384 } 385 386 private static void ensureAtMostSingleValue(FormField.Type type, List<FormField.Value> values) throws SmackParsingException { 387 if (values.size() > 1) { 388 throw new SmackParsingException(type + " fields can have at most one value, this one had " + values.size()); 389 } 390 } 391 392 private static void addOptionsToBuilder(Collection<FormField.Option> options, FormFieldWithOptions.Builder<?> builder) { 393 for (FormField.Option option : options) { 394 builder.addOption(option); 395 } 396 } 397}