ValidateElement.java

  1. /**
  2.  *
  3.  * Copyright 2014 Anno van Vliet, 2019-2020 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.xdatavalidation.packet;

  18. import java.math.BigInteger;

  19. import javax.xml.namespace.QName;

  20. import org.jivesoftware.smack.datatypes.UInt32;
  21. import org.jivesoftware.smack.packet.XmlElement;
  22. import org.jivesoftware.smack.packet.XmlEnvironment;
  23. import org.jivesoftware.smack.util.StringUtils;
  24. import org.jivesoftware.smack.util.XmlStringBuilder;

  25. import org.jivesoftware.smackx.xdata.AbstractSingleStringValueFormField;
  26. import org.jivesoftware.smackx.xdata.FormField;
  27. import org.jivesoftware.smackx.xdata.FormFieldChildElement;
  28. import org.jivesoftware.smackx.xdata.packet.DataForm;
  29. import org.jivesoftware.smackx.xdatavalidation.ValidationConsistencyException;

  30. /**
  31.  * DataValidation Extension according to XEP-0122: Data Forms Validation. This specification defines a
  32.  * backwards-compatible extension to the XMPP Data Forms protocol that enables applications to specify additional
  33.  * validation guidelines related to a {@link FormField} in a {@link DataForm}, such as validation of standard XML
  34.  * datatypes, application-specific datatypes, value ranges, and regular expressions.
  35.  *
  36.  * @author Anno van Vliet
  37.  */
  38. public abstract class ValidateElement implements FormFieldChildElement {

  39.     public static final String DATATYPE_XS_STRING = "xs:string";
  40.     public static final String ELEMENT = "validate";
  41.     public static final String NAMESPACE = "http://jabber.org/protocol/xdata-validate";

  42.     public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  43.     private final String datatype;

  44.     private ListRange listRange;

  45.     /**
  46.      * The 'datatype' attribute specifies the datatype. This attribute is OPTIONAL, and when not specified, defaults to
  47.      * "xs:string".
  48.      *
  49.      * @param datatype the data type of any value contained within the {@link FormField} element.
  50.      */
  51.     private ValidateElement(String datatype) {
  52.         this.datatype = StringUtils.isNotEmpty(datatype) ? datatype : null;
  53.     }

  54.     /**
  55.      * Specifies the data type of any value contained within the {@link FormField} element. It MUST meet one of the
  56.      * following conditions:
  57.      * <ul>
  58.      * <li>Start with "xs:", and be one of the "built-in" datatypes defined in XML Schema Part 2 <a
  59.      * href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1476016">[2]</a></li>
  60.      * <li>Start with a prefix registered with the XMPP Registrar <a
  61.      * href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1478544">[3]</a></li>
  62.      * <li>Start with "x:", and specify a user-defined datatype <a
  63.      * href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1477360">[4]</a></li>
  64.      * </ul>
  65.      *
  66.      * @return the datatype
  67.      */
  68.     public String getDatatype() {
  69.         return datatype != null ? datatype : DATATYPE_XS_STRING;
  70.     }

  71.     @Override
  72.     public String getElementName() {
  73.         return ELEMENT;
  74.     }

  75.     @Override
  76.     public String getNamespace() {
  77.         return NAMESPACE;
  78.     }

  79.     @Override
  80.     public QName getQName() {
  81.         return QNAME;
  82.     }

  83.     @Override
  84.     public final boolean mustBeOnlyOfHisKind() {
  85.         return true;
  86.     }

  87.     @Override
  88.     public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
  89.         XmlStringBuilder buf = new XmlStringBuilder(this, enclosingNamespace);
  90.         buf.optAttribute("datatype", datatype);
  91.         buf.rightAngleBracket();
  92.         appendXML(buf);
  93.         buf.optAppend(getListRange());
  94.         buf.closeElement(this);
  95.         return buf;
  96.     }

  97.     /**
  98.      * Append XML.
  99.      *
  100.      * @param buf TODO javadoc me please
  101.      */
  102.     protected abstract void appendXML(XmlStringBuilder buf);

  103.     /**
  104.      * Set list range.
  105.      * @param listRange the listRange to set
  106.      */
  107.     public void setListRange(ListRange listRange) {
  108.         this.listRange = listRange;
  109.     }

  110.     /**
  111.      * Get list range.
  112.      * @return the listRange
  113.      */
  114.     public ListRange getListRange() {
  115.         return listRange;
  116.     }

  117.     /**
  118.      * Check if this element is consistent according to the business rules in XEP-0122.
  119.      *
  120.      * @param formFieldBuilder the builder used to construct the form field.
  121.      */
  122.     @Override
  123.     public abstract void checkConsistency(FormField.Builder<?, ?> formFieldBuilder);

  124.     public static ValidateElement from(FormField formField) {
  125.         return (ValidateElement) formField.getFormFieldChildElement(QNAME);
  126.     }

  127.     /**
  128.      * Validation only against the datatype itself. Indicates that the value(s) should simply match the field type and
  129.      * datatype constraints.
  130.      *
  131.      * @see ValidateElement
  132.      */
  133.     public static class BasicValidateElement extends ValidateElement {

  134.         public static final String METHOD = "basic";

  135.         /**
  136.          * Basic validate element constructor.
  137.          * @param datatype TODO javadoc me please
  138.          * @see #getDatatype()
  139.          */
  140.         public BasicValidateElement(String datatype) {
  141.             super(datatype);
  142.         }

  143.         @Override
  144.         protected void appendXML(XmlStringBuilder buf) {
  145.             buf.emptyElement(METHOD);
  146.         }

  147.         @Override
  148.         public void checkConsistency(FormField.Builder<?, ?> formField) {
  149.             checkListRangeConsistency(formField);
  150.             if (formField.getType() != null) {
  151.                 switch (formField.getType()) {
  152.                 case hidden:
  153.                 case jid_multi:
  154.                 case jid_single:
  155.                     throw new ValidationConsistencyException(String.format(
  156.                                     "Field type '%1$s' is not consistent with validation method '%2$s'.",
  157.                                     formField.getType(), BasicValidateElement.METHOD));
  158.                 default:
  159.                     break;
  160.                 }
  161.             }
  162.         }

  163.     }

  164.     /**
  165.      * For "list-single" or "list-multi", indicates that the user may enter a custom value (matching the datatype
  166.      * constraints) or choose from the predefined values.
  167.      *
  168.      * @see ValidateElement
  169.      */
  170.     public static class OpenValidateElement extends ValidateElement {

  171.         public static final String METHOD = "open";

  172.         /**
  173.          * Open validate element constructor.
  174.          * @param datatype TODO javadoc me please
  175.          * @see #getDatatype()
  176.          */
  177.         public OpenValidateElement(String datatype) {
  178.             super(datatype);
  179.         }

  180.         @Override
  181.         protected void appendXML(XmlStringBuilder buf) {
  182.             buf.emptyElement(METHOD);
  183.         }

  184.         @Override
  185.         public void checkConsistency(FormField.Builder<?, ?> formField) {
  186.             checkListRangeConsistency(formField);
  187.             if (formField.getType() != null) {
  188.                 switch (formField.getType()) {
  189.                 case hidden:
  190.                     throw new ValidationConsistencyException(String.format(
  191.                                     "Field type '%1$s' is not consistent with validation method '%2$s'.",
  192.                                     formField.getType(), OpenValidateElement.METHOD));
  193.                 default:
  194.                     break;
  195.                 }
  196.             }
  197.         }

  198.     }

  199.     /**
  200.      * Indicate that the value should fall within a certain range.
  201.      *
  202.      * @see ValidateElement
  203.      */
  204.     public static class RangeValidateElement extends ValidateElement {

  205.         public static final String METHOD = "range";
  206.         private final String min;
  207.         private final String max;

  208.         /**
  209.          * Range validate element constructor.
  210.          * @param datatype TODO javadoc me please
  211.          * @param min the minimum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use.
  212.          * @param max the maximum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use.
  213.          * @see #getDatatype()
  214.          *
  215.          */
  216.         public RangeValidateElement(String datatype, String min, String max) {
  217.             super(datatype);
  218.             this.min = min;
  219.             this.max = max;
  220.         }

  221.         @Override
  222.         protected void appendXML(XmlStringBuilder buf) {
  223.             buf.halfOpenElement(METHOD);
  224.             buf.optAttribute("min", getMin());
  225.             buf.optAttribute("max", getMax());
  226.             buf.closeEmptyElement();
  227.         }

  228.         /**
  229.          * The 'min' attribute specifies the minimum allowable value.
  230.          *
  231.          * @return the minimum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use.
  232.          */
  233.         public String getMin() {
  234.             return min;
  235.         }

  236.         /**
  237.          * The 'max' attribute specifies the maximum allowable value.
  238.          *
  239.          * @return the maximum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use.
  240.          */
  241.         public String getMax() {
  242.             return max;
  243.         }

  244.         @Override
  245.         public void checkConsistency(FormField.Builder<?, ?> formField) {
  246.             checkNonMultiConsistency(formField, METHOD);
  247.             if (getDatatype().equals(ValidateElement.DATATYPE_XS_STRING)) {
  248.                 throw new ValidationConsistencyException(String.format(
  249.                                 "Field data type '%1$s' is not consistent with validation method '%2$s'.",
  250.                                 getDatatype(), RangeValidateElement.METHOD));
  251.             }
  252.         }

  253.         @Override
  254.         public void validate(FormField formField) {
  255.             AbstractSingleStringValueFormField singleValueFormField = formField.ifPossibleAs(AbstractSingleStringValueFormField.class);
  256.             if (singleValueFormField == null) {
  257.                 // We currently only implement validation for single value fields.
  258.                 return;
  259.             }
  260.             String valueString = singleValueFormField.getValue();

  261.             switch (getDatatype()) {
  262.             case "xs:int":
  263.             case "xs:integer":
  264.                 BigInteger value = new BigInteger(valueString);

  265.                 String minString = getMin();
  266.                 if (minString != null) {
  267.                     BigInteger min = new BigInteger(minString);
  268.                     if (value.compareTo(min) < 0) {
  269.                         throw new IllegalArgumentException("The provided value " + valueString + " is lower than the allowed minimum of " + minString);
  270.                     }
  271.                 }

  272.                 String maxString = getMax();
  273.                 if (maxString != null) {
  274.                     BigInteger max = new BigInteger(maxString);
  275.                     if (value.compareTo(max) > 0) {
  276.                         throw new IllegalArgumentException("The provided value " + valueString + " is higher than the allowed maximum of " + maxString);
  277.                     }
  278.                 }
  279.                 break;
  280.             }
  281.         }
  282.     }

  283.     /**
  284.      * Indicates that the value should be restricted to a regular expression. The regular expression must be that
  285.      * defined for <a href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1501344"> POSIX extended regular
  286.      * expressions </a> including support for <a
  287.      * href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1502496">Unicode</a>.
  288.      *
  289.      * @see ValidateElement
  290.      */
  291.     public static class RegexValidateElement extends ValidateElement {

  292.         public static final String METHOD = "regex";
  293.         private final String regex;

  294.         /**
  295.          * Regex validate element.
  296.          * @param datatype TODO javadoc me please
  297.          * @param regex TODO javadoc me please
  298.          * @see #getDatatype()
  299.          */
  300.         public RegexValidateElement(String datatype, String regex) {
  301.             super(datatype);
  302.             this.regex = regex;
  303.         }

  304.         /**
  305.          * the expression is that defined for POSIX extended regular expressions, including support for Unicode.
  306.          *
  307.          * @return the regex
  308.          */
  309.         public String getRegex() {
  310.             return regex;
  311.         }

  312.         @Override
  313.         protected void appendXML(XmlStringBuilder buf) {
  314.             buf.element("regex", getRegex());
  315.         }

  316.         @Override
  317.         public void checkConsistency(FormField.Builder<?, ?> formField) {
  318.             checkNonMultiConsistency(formField, METHOD);
  319.         }

  320.     }

  321.     /**
  322.      * This element indicates for "list-multi", that a minimum and maximum number of options should be selected and/or
  323.      * entered.
  324.      */
  325.     public static class ListRange implements XmlElement {

  326.         public static final String ELEMENT = "list-range";
  327.         private final UInt32 min;
  328.         private final UInt32 max;

  329.         public ListRange(Long min, Long max) {
  330.             this(min != null ? UInt32.from(min) : null, max != null ? UInt32.from(max) : null);
  331.         }

  332.         /**
  333.          * The 'max' attribute specifies the maximum allowable number of selected/entered values. The 'min' attribute
  334.          * specifies the minimum allowable number of selected/entered values. Both attributes are optional, but at
  335.          * least one must bet set, and the value must be within the range of a unsigned 32-bit integer.
  336.          *
  337.          * @param min TODO javadoc me please
  338.          * @param max TODO javadoc me please
  339.          */
  340.         public ListRange(UInt32 min, UInt32 max) {
  341.             if (max == null && min == null) {
  342.                 throw new IllegalArgumentException("Either min or max must be given");
  343.             }
  344.             this.min = min;
  345.             this.max = max;
  346.         }

  347.         @Override
  348.         public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) {
  349.             XmlStringBuilder buf = new XmlStringBuilder(this, enclosingXmlEnvironment);
  350.             buf.optAttributeCs("min", getMin());
  351.             buf.optAttributeCs("max", getMax());
  352.             buf.closeEmptyElement();
  353.             return buf;
  354.         }

  355.         @Override
  356.         public String getElementName() {
  357.             return ELEMENT;
  358.         }

  359.         /**
  360.          * The minimum allowable number of selected/entered values.
  361.          *
  362.          * @return a positive integer, can be null
  363.          */
  364.         public UInt32 getMin() {
  365.             return min;
  366.         }

  367.         /**
  368.          * The maximum allowable number of selected/entered values.
  369.          *
  370.          * @return a positive integer, can be null
  371.          */
  372.         public UInt32 getMax() {
  373.             return max;
  374.         }

  375.         @Override
  376.         public String getNamespace() {
  377.             return NAMESPACE;
  378.         }

  379.     }

  380.     /**
  381.      * The &gt;list-range/&lt; element SHOULD be included only when the &lt;field/&gt; is of type "list-multi" and SHOULD be ignored
  382.      * otherwise.
  383.      *
  384.      * @param formField TODO javadoc me please
  385.      */
  386.     protected void checkListRangeConsistency(FormField.Builder<?, ?> formField) {
  387.         ListRange listRange = getListRange();
  388.         if (listRange == null) {
  389.             return;
  390.         }

  391.         Object max = listRange.getMax();
  392.         Object min = listRange.getMin();
  393.         if ((max != null || min != null) && formField.getType() != FormField.Type.list_multi) {
  394.             throw new ValidationConsistencyException(
  395.                             "Field type is not of type 'list-multi' while a 'list-range' is defined.");
  396.         }
  397.     }

  398.     /**
  399.      * Check that the field being build is not of type multi (or hidden).
  400.      *
  401.      * @param formField TODO javadoc me please
  402.      * @param method TODO javadoc me please
  403.      */
  404.     protected void checkNonMultiConsistency(FormField.Builder<?, ?> formField, String method) {
  405.         checkListRangeConsistency(formField);
  406.         if (formField.getType() != null) {
  407.             switch (formField.getType()) {
  408.             case hidden:
  409.             case jid_multi:
  410.             case list_multi:
  411.             case text_multi:
  412.                 throw new ValidationConsistencyException(String.format(
  413.                                 "Field type '%1$s' is not consistent with validation method '%2$s'.",
  414.                                 formField.getType(), method));
  415.             default:
  416.                 break;
  417.             }
  418.         }
  419.     }
  420. }