mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-21 22:02:06 +01:00
Smack 4.4.1
-----BEGIN PGP SIGNATURE----- iQGTBAABCgB9FiEEl3UFnzoh3OFr5PuuIjmn6PWFIFIFAmA/eF1fFIAAAAAALgAo aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDk3 NzUwNTlGM0EyMURDRTE2QkU0RkJBRTIyMzlBN0U4RjU4NTIwNTIACgkQIjmn6PWF IFLlCQf6A9u5hd2v36Cve/Iopx/GAjuHBNu/7DYop7gP7vLtKxUBGCS/DLn8kaov ujpTvdcpm6pg7F4uivMnADFqkkL9cUwsBPDyNY8c0ygfGzVPU8P47g00/SBhB7kP 0/0In4eunXt8+tfs/afHrr1kQG66lq5J/9q8M6bbC9pUYO2g5Flxes0WW9azluVh zs39CCtXPeXwr6Lh+HNEyIe/WiHKnOj7zkwpV7htmOnPaedlRJj/e8KbcBDxmZhg zbMoI3Puq3grlA6spke6Z7HK6IeFZjbZABI4EeCEUVhUq1VvL0JurV92S672dty+ SXX64GbA5TLbVq04I+NP28x9kgnRiw== =skkJ -----END PGP SIGNATURE----- Merge tag '4.4.1' Smack 4.4.1
This commit is contained in:
commit
48f5e349b9
10 changed files with 315 additions and 109 deletions
|
@ -141,7 +141,33 @@ hr {
|
||||||
|
|
||||||
<div id="pageBody">
|
<div id="pageBody">
|
||||||
|
|
||||||
|
<h2>4.4.1 -- <span style="font-weight: normal;">2021-03-03</span></h2>
|
||||||
|
|
||||||
|
<h2> Bug
|
||||||
|
</h2>
|
||||||
|
<ul>
|
||||||
|
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-895'>SMACK-895</a>] - BoBIQ#getIQChildElementBuilder throws NPE when the BoB data does not contain ‘max-age’.
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-896'>SMACK-896</a>] - BoBDataExtension is missing getter for BoBData and ContentId
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-897'>SMACK-897</a>] - DirectoryRosterStore.readEntry() should also catch IllegalArgumentException
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-898'>SMACK-898</a>] - AbstractProvider should also consider TypeVariable
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-899'>SMACK-899</a>] - NullPointerException in EntityCapsManager.addCapsExtension
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-900'>SMACK-900</a>] - NPE in DataForm.Builder.addItem()
|
||||||
|
</li>
|
||||||
|
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-902'>SMACK-902</a>] - DataFormProvider should retrieve the type of fields from <reported/> elements if possible
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2> Improvement
|
||||||
|
</h2>
|
||||||
|
<ul>
|
||||||
|
<li>[<a href='https://igniterealtime.atlassian.net/browse/SMACK-901'>SMACK-901</a>] - BoBDataExtension.from() should also allow IQs
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<h2>4.4.0 -- <span style="font-weight: normal;">2020-12-06</span></h2>
|
<h2>4.4.0 -- <span style="font-weight: normal;">2020-12-06</span></h2>
|
||||||
|
|
||||||
|
|
|
@ -64,4 +64,13 @@ public class XmlUtil {
|
||||||
|
|
||||||
return stringWriter.toString();
|
return stringWriter.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isClarkNotation(String text) {
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This is currently a mediocre heuristic to check for clark notation.
|
||||||
|
return text.charAt(0) == '{';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,8 +360,7 @@ public final class FileTransferNegotiator extends Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DataForm createDefaultInitiationForm() {
|
private static DataForm createDefaultInitiationForm() {
|
||||||
DataForm.Builder form = DataForm.builder()
|
DataForm.Builder form = DataForm.builder(DataForm.Type.form);
|
||||||
.setType(DataForm.Type.form);
|
|
||||||
ListSingleFormField.Builder fieldBuilder = FormField.listSingleBuilder(STREAM_DATA_FIELD_NAME);
|
ListSingleFormField.Builder fieldBuilder = FormField.listSingleBuilder(STREAM_DATA_FIELD_NAME);
|
||||||
|
|
||||||
if (!IBB_ONLY) {
|
if (!IBB_ONLY) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2020 Florian Schmaus
|
* Copyright 2020-2021 Florian Schmaus
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,27 +18,26 @@ package org.jivesoftware.smackx.formtypes;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.jivesoftware.smack.util.Objects;
|
import org.jivesoftware.smack.util.Objects;
|
||||||
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
import org.jivesoftware.smack.util.XmlUtil;
|
||||||
import org.jivesoftware.smackx.xdata.FormField;
|
import org.jivesoftware.smackx.xdata.FormField;
|
||||||
import org.jivesoftware.smackx.xdata.TextSingleFormField;
|
import org.jivesoftware.smackx.xdata.TextSingleFormField;
|
||||||
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||||
|
|
||||||
public class FormFieldRegistry {
|
public class FormFieldRegistry {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(FormFieldRegistry.class.getName());
|
||||||
|
|
||||||
private static final Map<String, Map<String, FormField.Type>> REGISTRY = new HashMap<>();
|
private static final Map<String, Map<String, FormField.Type>> REGISTRY = new HashMap<>();
|
||||||
|
|
||||||
private static final Map<String, FormField.Type> LOOKASIDE_REGISTRY = new HashMap<>();
|
private static final Map<String, FormField.Type> CLARK_NOTATION_FIELD_REGISTRY = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private static final Map<String, String> FIELD_NAME_TO_FORM_TYPE = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
register(FormField.FORM_TYPE, FormField.Type.hidden);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ReferenceEquality")
|
@SuppressWarnings("ReferenceEquality")
|
||||||
public static synchronized void register(DataForm dataForm) {
|
public static void register(DataForm dataForm) {
|
||||||
// TODO: Also allow forms of type 'result'?
|
// TODO: Also allow forms of type 'result'?
|
||||||
if (dataForm.getType() != DataForm.Type.form) {
|
if (dataForm.getType() != DataForm.Type.form) {
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
|
@ -56,64 +55,57 @@ public class FormFieldRegistry {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
String fieldName = formField.getFieldName();
|
|
||||||
FormField.Type type = formField.getType();
|
FormField.Type type = formField.getType();
|
||||||
|
if (type == FormField.Type.fixed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fieldName = formField.getFieldName();
|
||||||
register(formType, fieldName, type);
|
register(formType, fieldName, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized void register(String formType, String fieldName, FormField.Type type) {
|
public static void register(String formType, String fieldName, FormField.Type fieldType) {
|
||||||
|
StringUtils.requireNotNullNorEmpty(fieldName, "fieldName must be provided");
|
||||||
|
Objects.requireNonNull(fieldType);
|
||||||
|
|
||||||
if (formType == null) {
|
if (formType == null) {
|
||||||
FormFieldInformation formFieldInformation = lookup(fieldName);
|
if (XmlUtil.isClarkNotation(fieldName)) {
|
||||||
if (formFieldInformation != null) {
|
CLARK_NOTATION_FIELD_REGISTRY.put(fieldName, fieldType);
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, FormField.Type> fieldNameToType = REGISTRY.get(formType);
|
FormField.Type previousType;
|
||||||
if (fieldNameToType == null) {
|
synchronized (REGISTRY) {
|
||||||
fieldNameToType = new HashMap<>();
|
Map<String, FormField.Type> fieldNameToType = REGISTRY.get(formType);
|
||||||
REGISTRY.put(formType, fieldNameToType);
|
if (fieldNameToType == null) {
|
||||||
} else {
|
fieldNameToType = new HashMap<>();
|
||||||
FormField.Type previousType = fieldNameToType.get(fieldName);
|
REGISTRY.put(formType, fieldNameToType);
|
||||||
if (previousType != null && previousType != type) {
|
} else {
|
||||||
throw new IllegalArgumentException();
|
previousType = fieldNameToType.get(fieldName);
|
||||||
|
if (previousType != null && previousType != fieldType) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
previousType = fieldNameToType.put(fieldName, fieldType);
|
||||||
}
|
}
|
||||||
fieldNameToType.put(fieldName, type);
|
if (previousType != null && fieldType != previousType) {
|
||||||
|
LOGGER.warning("Form field registry inconsitency detected: Registered field '" + fieldName + "' of type " + fieldType + " but previous type was " + previousType);
|
||||||
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) {
|
public static FormField.Type lookup(String formType, String fieldName) {
|
||||||
if (formType != null) {
|
if (formType == null) {
|
||||||
|
if (!XmlUtil.isClarkNotation(fieldName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CLARK_NOTATION_FIELD_REGISTRY.get(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (REGISTRY) {
|
||||||
Map<String, FormField.Type> fieldNameToTypeMap = REGISTRY.get(formType);
|
Map<String, FormField.Type> fieldNameToTypeMap = REGISTRY.get(formType);
|
||||||
if (fieldNameToTypeMap != null) {
|
if (fieldNameToTypeMap != null) {
|
||||||
FormField.Type type = fieldNameToTypeMap.get(fieldName);
|
FormField.Type type = fieldNameToTypeMap.get(fieldName);
|
||||||
|
@ -121,38 +113,13 @@ public class FormFieldRegistry {
|
||||||
return type;
|
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 null;
|
||||||
return LOOKASIDE_REGISTRY.get(fieldName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized FormFieldInformation lookup(String fieldName) {
|
public static synchronized FormField.Type lookup(String fieldName) {
|
||||||
String formType = FIELD_NAME_TO_FORM_TYPE.get(fieldName);
|
return lookup(null, 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -255,11 +255,16 @@ public class FillableForm extends FilledForm {
|
||||||
if (!missingRequiredFields.isEmpty()) {
|
if (!missingRequiredFields.isEmpty()) {
|
||||||
throw new IllegalStateException("Not all required fields filled. Missing: " + missingRequiredFields);
|
throw new IllegalStateException("Not all required fields filled. Missing: " + missingRequiredFields);
|
||||||
}
|
}
|
||||||
DataForm dataFormToSend = DataForm.builder()
|
DataForm.Builder builder = DataForm.builder();
|
||||||
.addField(formTypeFormField)
|
|
||||||
.addFields(filledFields.values())
|
// the submit form has the same FORM_TYPE as the form.
|
||||||
.build();
|
if (formTypeFormField != null) {
|
||||||
return dataFormToSend;
|
builder.addField(formTypeFormField);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.addFields(filledFields.values());
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package org.jivesoftware.smackx.xdata.form;
|
package org.jivesoftware.smackx.xdata.form;
|
||||||
|
|
||||||
import org.jivesoftware.smack.util.Objects;
|
import org.jivesoftware.smack.util.Objects;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
|
||||||
|
|
||||||
import org.jivesoftware.smackx.xdata.FormField;
|
import org.jivesoftware.smackx.xdata.FormField;
|
||||||
import org.jivesoftware.smackx.xdata.TextSingleFormField;
|
import org.jivesoftware.smackx.xdata.TextSingleFormField;
|
||||||
|
@ -31,10 +30,6 @@ public abstract class FilledForm implements FormReader {
|
||||||
|
|
||||||
public FilledForm(DataForm dataForm) {
|
public FilledForm(DataForm dataForm) {
|
||||||
this.dataForm = Objects.requireNonNull(dataForm);
|
this.dataForm = Objects.requireNonNull(dataForm);
|
||||||
String formType = dataForm.getFormType();
|
|
||||||
if (StringUtils.isNullOrEmpty(formType)) {
|
|
||||||
throw new IllegalArgumentException("The provided data form has no hidden FROM_TYPE field.");
|
|
||||||
}
|
|
||||||
if (dataForm.getType() == DataForm.Type.cancel) {
|
if (dataForm.getType() == DataForm.Type.cancel) {
|
||||||
throw new IllegalArgumentException("Forms of type 'cancel' are not filled nor fillable");
|
throw new IllegalArgumentException("Forms of type 'cancel' are not filled nor fillable");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2003-2007 Jive Software, 2020 Florian Schmaus.
|
* Copyright 2003-2007 Jive Software, 2020-2021 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -100,6 +100,9 @@ public final class DataForm implements ExtensionElement {
|
||||||
instructions = CollectionUtil.cloneAndSeal(builder.instructions);
|
instructions = CollectionUtil.cloneAndSeal(builder.instructions);
|
||||||
reportedData = builder.reportedData;
|
reportedData = builder.reportedData;
|
||||||
items = CollectionUtil.cloneAndSeal(builder.items);
|
items = CollectionUtil.cloneAndSeal(builder.items);
|
||||||
|
|
||||||
|
builder.orderFields();
|
||||||
|
|
||||||
fields = CollectionUtil.cloneAndSeal(builder.fields);
|
fields = CollectionUtil.cloneAndSeal(builder.fields);
|
||||||
fieldsMap = CollectionUtil.cloneAndSeal(builder.fieldsMap);
|
fieldsMap = CollectionUtil.cloneAndSeal(builder.fieldsMap);
|
||||||
extensionElements = CollectionUtil.cloneAndSeal(builder.extensionElements);
|
extensionElements = CollectionUtil.cloneAndSeal(builder.extensionElements);
|
||||||
|
@ -353,6 +356,7 @@ public final class DataForm implements ExtensionElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
|
// TODO: Make this field final once setType() is gone.
|
||||||
private Type type;
|
private Type type;
|
||||||
private String title;
|
private String title;
|
||||||
private List<String> instructions;
|
private List<String> instructions;
|
||||||
|
@ -381,6 +385,39 @@ public final class DataForm implements ExtensionElement {
|
||||||
extensionElements = CollectionUtil.newListWith(dataForm.getExtensionElements());
|
extensionElements = CollectionUtil.newListWith(dataForm.getExtensionElements());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void orderFields() {
|
||||||
|
Iterator<FormField> it = fields.iterator();
|
||||||
|
if (!it.hasNext()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FormField hiddenFormTypeField = it.next().asHiddenFormTypeFieldIfPossible();
|
||||||
|
if (hiddenFormTypeField != null) {
|
||||||
|
// The hidden FROM_TYPE field is already in first position, nothing to do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (it.hasNext()) {
|
||||||
|
hiddenFormTypeField = it.next().asHiddenFormTypeFieldIfPossible();
|
||||||
|
if (hiddenFormTypeField != null) {
|
||||||
|
// Remove the hidden FORM_TYPE field that is not on first position.
|
||||||
|
it.remove();
|
||||||
|
// And insert it again at first position.
|
||||||
|
fields.add(0, hiddenFormTypeField);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deprecated do not use.
|
||||||
|
*
|
||||||
|
* @param type the type.
|
||||||
|
* @return a reference to this builder.
|
||||||
|
* @deprecated use {@link DataForm#builder(Type)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
// TODO: Remove in Smack 4.5 and then make this.type final.
|
||||||
public Builder setType(Type type) {
|
public Builder setType(Type type) {
|
||||||
this.type = Objects.requireNonNull(type);
|
this.type = Objects.requireNonNull(type);
|
||||||
return this;
|
return this;
|
||||||
|
@ -538,6 +575,8 @@ public final class DataForm implements ExtensionElement {
|
||||||
|
|
||||||
private final List<? extends FormField> fields;
|
private final List<? extends FormField> fields;
|
||||||
|
|
||||||
|
private Map<String, FormField> fieldMap;
|
||||||
|
|
||||||
public ReportedData(List<? extends FormField> fields) {
|
public ReportedData(List<? extends FormField> fields) {
|
||||||
this.fields = Collections.unmodifiableList(fields);
|
this.fields = Collections.unmodifiableList(fields);
|
||||||
}
|
}
|
||||||
|
@ -561,6 +600,18 @@ public final class DataForm implements ExtensionElement {
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FormField getField(String name) {
|
||||||
|
if (fieldMap == null) {
|
||||||
|
fieldMap = new HashMap<>(fields.size());
|
||||||
|
for (FormField field : fields) {
|
||||||
|
String fieldName = field.getFieldName();
|
||||||
|
fieldMap.put(fieldName, field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldMap.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
|
||||||
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
|
XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Copyright 2003-2007 Jive Software 2020 Florian Schmaus.
|
* Copyright 2003-2007 Jive Software 2020-2021 Florian Schmaus.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -66,10 +66,10 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
|
||||||
@Override
|
@Override
|
||||||
public DataForm parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
|
public DataForm parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException {
|
||||||
DataForm.Type dataFormType = DataForm.Type.fromString(parser.getAttributeValue("", "type"));
|
DataForm.Type dataFormType = DataForm.Type.fromString(parser.getAttributeValue("", "type"));
|
||||||
DataForm.Builder dataForm = DataForm.builder();
|
DataForm.Builder dataForm = DataForm.builder(dataFormType);
|
||||||
dataForm.setType(dataFormType);
|
|
||||||
|
|
||||||
String formType = null;
|
String formType = null;
|
||||||
|
DataForm.ReportedData reportedData = null;
|
||||||
|
|
||||||
outerloop: while (true) {
|
outerloop: while (true) {
|
||||||
XmlPullParser.Event eventType = parser.next();
|
XmlPullParser.Event eventType = parser.next();
|
||||||
|
@ -86,6 +86,8 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
|
||||||
dataForm.setTitle(parser.nextText());
|
dataForm.setTitle(parser.nextText());
|
||||||
break;
|
break;
|
||||||
case "field":
|
case "field":
|
||||||
|
// Note that we parse this form field without any potential reportedData. We only use reportedData
|
||||||
|
// to lookup form field types of fields under <item/>.
|
||||||
FormField formField = parseField(parser, elementXmlEnvironment, formType);
|
FormField formField = parseField(parser, elementXmlEnvironment, formType);
|
||||||
|
|
||||||
TextSingleFormField hiddenFormTypeField = formField.asHiddenFormTypeFieldIfPossible();
|
TextSingleFormField hiddenFormTypeField = formField.asHiddenFormTypeFieldIfPossible();
|
||||||
|
@ -99,12 +101,15 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
|
||||||
dataForm.addField(formField);
|
dataForm.addField(formField);
|
||||||
break;
|
break;
|
||||||
case "item":
|
case "item":
|
||||||
DataForm.Item item = parseItem(parser, elementXmlEnvironment, formType);
|
DataForm.Item item = parseItem(parser, elementXmlEnvironment, formType, reportedData);
|
||||||
dataForm.addItem(item);
|
dataForm.addItem(item);
|
||||||
break;
|
break;
|
||||||
case "reported":
|
case "reported":
|
||||||
DataForm.ReportedData reported = parseReported(parser, elementXmlEnvironment, formType);
|
if (reportedData != null) {
|
||||||
dataForm.setReportedData(reported);
|
throw new SmackParsingException("Data form with multiple <reported/> elements");
|
||||||
|
}
|
||||||
|
reportedData = parseReported(parser, elementXmlEnvironment, formType);
|
||||||
|
dataForm.setReportedData(reportedData);
|
||||||
break;
|
break;
|
||||||
// See XEP-133 Example 32 for a corner case where the data form contains this extension.
|
// See XEP-133 Example 32 for a corner case where the data form contains this extension.
|
||||||
case RosterPacket.ELEMENT:
|
case RosterPacket.ELEMENT:
|
||||||
|
@ -135,6 +140,11 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
|
||||||
|
|
||||||
private static FormField parseField(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType)
|
private static FormField parseField(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType)
|
||||||
throws XmlPullParserException, IOException, SmackParsingException {
|
throws XmlPullParserException, IOException, SmackParsingException {
|
||||||
|
return parseField(parser, xmlEnvironment, formType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FormField parseField(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType, DataForm.ReportedData reportedData)
|
||||||
|
throws XmlPullParserException, IOException, SmackParsingException {
|
||||||
final int initialDepth = parser.getDepth();
|
final int initialDepth = parser.getDepth();
|
||||||
|
|
||||||
final String fieldName = parser.getAttributeValue("var");
|
final String fieldName = parser.getAttributeValue("var");
|
||||||
|
@ -186,15 +196,30 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Data forms of type 'result' may contain <reported/> and <item/> elements. If this is the case, then the type
|
||||||
|
// of the <field/>s within the <item/> elements is determined by the information found in <reported/>. See
|
||||||
|
// XEP-0004 § 3.4 and SMACK-902
|
||||||
|
if (type == null && reportedData != null) {
|
||||||
|
FormField reportedFormField = reportedData.getField(fieldName);
|
||||||
|
if (reportedFormField != null) {
|
||||||
|
type = reportedFormField.getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
// If no field type was explicitly provided, then we need to lookup the
|
// The field name 'FORM_TYPE' is magic.
|
||||||
// field's type in the registry.
|
if (fieldName.equals(FormField.FORM_TYPE)) {
|
||||||
type = FormFieldRegistry.lookup(formType, fieldName);
|
type = FormField.Type.hidden;
|
||||||
if (type == null) {
|
} else {
|
||||||
LOGGER.warning("The Field '" + fieldName + "' from FORM_TYPE '" + formType
|
// If no field type was explicitly provided, then we need to lookup the
|
||||||
+ "' is not registered. Field type is unknown, assuming text-single.");
|
// field's type in the registry.
|
||||||
// As per XEP-0004, text-single is the default form field type, which we use as emergency fallback here.
|
type = FormFieldRegistry.lookup(formType, fieldName);
|
||||||
type = FormField.Type.text_single;
|
if (type == null) {
|
||||||
|
LOGGER.warning("The Field '" + fieldName + "' from FORM_TYPE '" + formType
|
||||||
|
+ "' is not registered. Field type is unknown, assuming text-single.");
|
||||||
|
// As per XEP-0004, text-single is the default form field type, which we use as emergency fallback here.
|
||||||
|
type = FormField.Type.text_single;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,7 +326,8 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DataForm.Item parseItem(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType)
|
private static DataForm.Item parseItem(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType,
|
||||||
|
DataForm.ReportedData reportedData)
|
||||||
throws XmlPullParserException, IOException, SmackParsingException {
|
throws XmlPullParserException, IOException, SmackParsingException {
|
||||||
final int initialDepth = parser.getDepth();
|
final int initialDepth = parser.getDepth();
|
||||||
List<FormField> fields = new ArrayList<>();
|
List<FormField> fields = new ArrayList<>();
|
||||||
|
@ -312,7 +338,8 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
|
||||||
String name = parser.getName();
|
String name = parser.getName();
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "field":
|
case "field":
|
||||||
FormField field = parseField(parser, XmlEnvironment.from(parser, xmlEnvironment), formType);
|
FormField field = parseField(parser, XmlEnvironment.from(parser, xmlEnvironment), formType,
|
||||||
|
reportedData);
|
||||||
fields.add(field);
|
fields.add(field);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,4 +152,14 @@ public class DataFormTest extends SmackTestSuite {
|
||||||
DataForm df = pr.parse(PacketParserUtils.getParserFor(formWithFixedField));
|
DataForm df = pr.parse(PacketParserUtils.getParserFor(formWithFixedField));
|
||||||
assertEquals(FormField.Type.fixed, df.getFields().get(0).getType());
|
assertEquals(FormField.Type.fixed, df.getFields().get(0).getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReorderHiddenFormTypeFieldAtFirstPosition() {
|
||||||
|
DataForm dataForm = DataForm.builder()
|
||||||
|
.addField(FormField.textSingleBuilder("foo1").setValue("bar").build())
|
||||||
|
.addField(FormField.textSingleBuilder("foo2").setValue("baz").build())
|
||||||
|
.setFormType("my-form-type")
|
||||||
|
.build();
|
||||||
|
assertNotNull(dataForm.getFields().get(0).asHiddenFormTypeFieldIfPossible());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Copyright 2021 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.provider;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||||
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParser;
|
||||||
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
|
import org.jivesoftware.smackx.xdata.FormField;
|
||||||
|
import org.jivesoftware.smackx.xdata.packet.DataForm;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
public class DataFormProviderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRetrieveFieldTypeFromReported() throws XmlPullParserException, IOException, SmackParsingException {
|
||||||
|
|
||||||
|
String firstForm =
|
||||||
|
"<x xmlns='jabber:x:data' type='form'>" +
|
||||||
|
" <title>Advanced User Search</title>" +
|
||||||
|
" <instructions>The following fields are available for searching. Wildcard (*) characters are allowed as part of the query.</instructions>" +
|
||||||
|
" <field var='FORM_TYPE' type='hidden'>" +
|
||||||
|
" <value>jabber:iq:search</value>" +
|
||||||
|
" </field>" +
|
||||||
|
" <field label='Search' var='search'>" +
|
||||||
|
" <required/>" +
|
||||||
|
" </field>" +
|
||||||
|
" <field label='Username' var='Username' type='boolean'>" +
|
||||||
|
" <value>true</value>" +
|
||||||
|
" </field>" +
|
||||||
|
" <field label='Name' var='Name' type='boolean'>" +
|
||||||
|
" <value>true</value>" +
|
||||||
|
" </field>" +
|
||||||
|
" <field label='Email' var='Email' type='boolean'>" +
|
||||||
|
" <value>true</value>" +
|
||||||
|
" </field>" +
|
||||||
|
"</x>";
|
||||||
|
XmlPullParser parser = PacketParserUtils.getParserFor(firstForm);
|
||||||
|
DataForm firstDataForm = DataFormProvider.INSTANCE.parse(parser);
|
||||||
|
FormField usernameFormField = firstDataForm.getField("Username");
|
||||||
|
assertEquals(FormField.Type.bool, usernameFormField.getType());
|
||||||
|
|
||||||
|
String secondForm =
|
||||||
|
"<x xmlns='jabber:x:data' type='result'>" +
|
||||||
|
" <field var='FORM_TYPE' type='hidden'/>" +
|
||||||
|
" <reported>" +
|
||||||
|
" <field var='jid' type='jid-single' label='JID'/>" +
|
||||||
|
" <field var='Username' type='text-single' label='Username'/>" +
|
||||||
|
" <field var='Name' type='text-single' label='Name'/>" +
|
||||||
|
" <field var='Email' type='text-single' label='Email'/>" +
|
||||||
|
" </reported>" +
|
||||||
|
" <item>" +
|
||||||
|
" <field var='Email'>" +
|
||||||
|
" <value>" +
|
||||||
|
" 0" +
|
||||||
|
" </value>" +
|
||||||
|
" </field>" +
|
||||||
|
" <field var='jid'>" +
|
||||||
|
" <value>frank@orphu</value>" +
|
||||||
|
" </field>" +
|
||||||
|
" <field var='Username'>" +
|
||||||
|
" <value>" +
|
||||||
|
" frank" +
|
||||||
|
" </value>" +
|
||||||
|
" </field>" +
|
||||||
|
" <field var='Name'>" +
|
||||||
|
" <value>" +
|
||||||
|
" 0" +
|
||||||
|
" </value>" +
|
||||||
|
" </field>" +
|
||||||
|
" </item>" +
|
||||||
|
" <item>" +
|
||||||
|
" <field var='Email'>" +
|
||||||
|
" <value>" +
|
||||||
|
" </value>" +
|
||||||
|
" </field>" +
|
||||||
|
" <field var='jid'>" +
|
||||||
|
" <value>frank2@orphu</value>" +
|
||||||
|
" </field>" +
|
||||||
|
" <field var='Username'>" +
|
||||||
|
" <value>" +
|
||||||
|
" frank2" +
|
||||||
|
" </value>" +
|
||||||
|
" </field>" +
|
||||||
|
" <field var='Name'>" +
|
||||||
|
" <value>" +
|
||||||
|
" </value>" +
|
||||||
|
" </field>" +
|
||||||
|
" </item>" +
|
||||||
|
"</x>";
|
||||||
|
parser = PacketParserUtils.getParserFor(secondForm);
|
||||||
|
DataForm secondDataForm = DataFormProvider.INSTANCE.parse(parser);
|
||||||
|
List<DataForm.Item> items = secondDataForm.getItems();
|
||||||
|
assertEquals(2, items.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue