ValidateElement.java

  1. /**
  2.  *
  3.  * Copyright 2014 Anno van Vliet
  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 org.jivesoftware.smack.packet.NamedElement;
  19. import org.jivesoftware.smack.packet.ExtensionElement;
  20. import org.jivesoftware.smack.util.NumberUtil;
  21. import org.jivesoftware.smack.util.StringUtils;
  22. import org.jivesoftware.smack.util.XmlStringBuilder;
  23. import org.jivesoftware.smackx.xdata.FormField;
  24. import org.jivesoftware.smackx.xdata.packet.DataForm;
  25. import org.jivesoftware.smackx.xdatavalidation.ValidationConsistencyException;

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

  35.     public static final String DATATYPE_XS_STRING = "xs:string";
  36.     public static final String ELEMENT = "validate";
  37.     public static final String NAMESPACE = "http://jabber.org/protocol/xdata-validate";

  38.     private final String datatype;

  39.     private ListRange listRange;

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

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

  66.     @Override
  67.     public String getElementName() {
  68.         return ELEMENT;
  69.     }

  70.     @Override
  71.     public String getNamespace() {
  72.         return NAMESPACE;
  73.     }

  74.     @Override
  75.     public XmlStringBuilder toXML() {
  76.         XmlStringBuilder buf = new XmlStringBuilder(this);
  77.         buf.optAttribute("datatype", datatype);
  78.         buf.rightAngleBracket();
  79.         appendXML(buf);
  80.         buf.optAppend(getListRange());
  81.         buf.closeElement(this);
  82.         return buf;
  83.     }

  84.     /**
  85.      * @param buf
  86.      */
  87.     protected abstract void appendXML(XmlStringBuilder buf);

  88.     /**
  89.      * @param listRange the listRange to set
  90.      */
  91.     public void setListRange(ListRange listRange) {
  92.         this.listRange = listRange;
  93.     }

  94.     /**
  95.      * @return the listRange
  96.      */
  97.     public ListRange getListRange() {
  98.         return listRange;
  99.     }

  100.     /**
  101.      * Check if this element is consistent according to the business rules in XEP=0122
  102.      *
  103.      * @param formField
  104.      */
  105.     public abstract void checkConsistency(FormField formField);

  106.     /**
  107.      * Validation only against the datatype itself. Indicates that the value(s) should simply match the field type and
  108.      * datatype constraints.
  109.      *
  110.      * @see ValidateElement
  111.      */
  112.     public static class BasicValidateElement extends ValidateElement {

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

  114.         /**
  115.          * @param dataType
  116.          * @see #getDatatype()
  117.          */
  118.         public BasicValidateElement(String dataType) {
  119.             super(dataType);
  120.         }

  121.         @Override
  122.         protected void appendXML(XmlStringBuilder buf) {
  123.             buf.emptyElement(METHOD);
  124.         }

  125.         public void checkConsistency(FormField formField) {
  126.             checkListRangeConsistency(formField);
  127.             if (formField.getType() != null) {
  128.                 switch (formField.getType()) {
  129.                 case hidden:
  130.                 case jid_multi:
  131.                 case jid_single:
  132.                     throw new ValidationConsistencyException(String.format(
  133.                                     "Field type '%1$s' is not consistent with validation method '%2$s'.",
  134.                                     formField.getType(), BasicValidateElement.METHOD));
  135.                 default:
  136.                     break;
  137.                 }
  138.             }
  139.         }

  140.     }

  141.     /**
  142.      * For "list-single" or "list-multi", indicates that the user may enter a custom value (matching the datatype
  143.      * constraints) or choose from the predefined values.
  144.      *
  145.      * @see ValidateElement
  146.      */
  147.     public static class OpenValidateElement extends ValidateElement {

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

  149.         /**
  150.          * @param dataType
  151.          * @see #getDatatype()
  152.          */
  153.         public OpenValidateElement(String dataType) {
  154.             super(dataType);
  155.         }

  156.         @Override
  157.         protected void appendXML(XmlStringBuilder buf) {
  158.             buf.emptyElement(METHOD);
  159.         }

  160.         public void checkConsistency(FormField formField) {
  161.             checkListRangeConsistency(formField);
  162.             if (formField.getType() != null) {
  163.                 switch (formField.getType()) {
  164.                 case hidden:
  165.                     throw new ValidationConsistencyException(String.format(
  166.                                     "Field type '%1$s' is not consistent with validation method '%2$s'.",
  167.                                     formField.getType(), OpenValidateElement.METHOD));
  168.                 default:
  169.                     break;
  170.                 }
  171.             }
  172.         }

  173.     }

  174.     /**
  175.      * Indicate that the value should fall within a certain range.
  176.      *
  177.      * @see ValidateElement
  178.      */
  179.     public static class RangeValidateElement extends ValidateElement {

  180.         public static final String METHOD = "range";
  181.         private final String min;
  182.         private final String max;

  183.         /**
  184.          * @param dataType
  185.          * @param min the minimum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use.
  186.          * @param max the maximum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use.
  187.          * @see #getDatatype()
  188.          *
  189.          */
  190.         public RangeValidateElement(String dataType, String min, String max) {
  191.             super(dataType);
  192.             this.min = min;
  193.             this.max = max;
  194.         }

  195.         @Override
  196.         protected void appendXML(XmlStringBuilder buf) {
  197.             buf.halfOpenElement(METHOD);
  198.             buf.optAttribute("min", getMin());
  199.             buf.optAttribute("max", getMax());
  200.             buf.closeEmptyElement();
  201.         }

  202.         /**
  203.          * The 'min' attribute specifies the minimum allowable value.
  204.          *
  205.          * @return the minimum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use.
  206.          */
  207.         public String getMin() {
  208.             return min;
  209.         }

  210.         /**
  211.          * The 'max' attribute specifies the maximum allowable value.
  212.          *
  213.          * @return the maximum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use.
  214.          */
  215.         public String getMax() {
  216.             return max;
  217.         }

  218.         public void checkConsistency(FormField formField) {
  219.             checkNonMultiConsistency(formField, METHOD);
  220.             if (getDatatype().equals(ValidateElement.DATATYPE_XS_STRING)) {
  221.                 throw new ValidationConsistencyException(String.format(
  222.                                 "Field data type '%1$s' is not consistent with validation method '%2$s'.",
  223.                                 getDatatype(), RangeValidateElement.METHOD));
  224.             }
  225.         }

  226.     }

  227.     /**
  228.      * Indicates that the value should be restricted to a regular expression. The regular expression must be that
  229.      * defined for <a href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1501344"> POSIX extended regular
  230.      * expressions </a> including support for <a
  231.      * href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1502496">Unicode</a>.
  232.      *
  233.      * @see ValidateElement
  234.      */
  235.     public static class RegexValidateElement extends ValidateElement {

  236.         public static final String METHOD = "regex";
  237.         private final String regex;

  238.         /**
  239.          * @param dataType
  240.          * @param regex
  241.          * @see #getDatatype()
  242.          */
  243.         public RegexValidateElement(String dataType, String regex) {
  244.             super(dataType);
  245.             this.regex = regex;
  246.         }

  247.         /**
  248.          * the expression is that defined for POSIX extended regular expressions, including support for Unicode.
  249.          *
  250.          * @return the regex
  251.          */
  252.         public String getRegex() {
  253.             return regex;
  254.         }

  255.         @Override
  256.         protected void appendXML(XmlStringBuilder buf) {
  257.             buf.element("regex", getRegex());
  258.         }

  259.         public void checkConsistency(FormField formField) {
  260.             checkNonMultiConsistency(formField, METHOD);
  261.         }

  262.     }

  263.     /**
  264.      * This element indicates for "list-multi", that a minimum and maximum number of options should be selected and/or
  265.      * entered.
  266.      */
  267.     public static class ListRange implements NamedElement {

  268.         public static final String ELEMENT = "list-range";
  269.         private final Long min;
  270.         private final Long max;

  271.         /**
  272.          * The 'max' attribute specifies the maximum allowable number of selected/entered values. The 'min' attribute
  273.          * specifies the minimum allowable number of selected/entered values. Both attributes are optional, but at
  274.          * least one must bet set, and the value must be within the range of a unsigned 32-bit integer.
  275.          *
  276.          * @param min
  277.          * @param max
  278.          */
  279.         public ListRange(Long min, Long max) {
  280.             if (min != null) {
  281.                 NumberUtil.checkIfInUInt32Range(min);
  282.             }
  283.             if (max != null) {
  284.                 NumberUtil.checkIfInUInt32Range(max);
  285.             }
  286.             if (max == null && min == null) {
  287.                 throw new IllegalArgumentException("Either min or max must be given");
  288.             }
  289.             this.min = min;
  290.             this.max = max;
  291.         }

  292.         public XmlStringBuilder toXML() {
  293.             XmlStringBuilder buf = new XmlStringBuilder(this);
  294.             buf.optLongAttribute("min", getMin());
  295.             buf.optLongAttribute("max", getMax());
  296.             buf.closeEmptyElement();
  297.             return buf;
  298.         }

  299.         @Override
  300.         public String getElementName() {
  301.             return ELEMENT;
  302.         }

  303.         /**
  304.          * The minimum allowable number of selected/entered values.
  305.          *
  306.          * @return a positive integer, can be null
  307.          */
  308.         public Long getMin() {
  309.             return min;
  310.         }

  311.         /**
  312.          * The maximum allowable number of selected/entered values.
  313.          *
  314.          * @return a positive integer, can be null
  315.          */
  316.         public Long getMax() {
  317.             return max;
  318.         }

  319.     }

  320.     /**
  321.      * The <list-range/> element SHOULD be included only when the <field/> is of type "list-multi" and SHOULD be ignored
  322.      * otherwise.
  323.      *
  324.      * @param formField
  325.      */
  326.     protected void checkListRangeConsistency(FormField formField) {
  327.         ListRange listRange = getListRange();
  328.         if (listRange == null) {
  329.             return;
  330.         }

  331.         Long max = listRange.getMax();
  332.         Long min = listRange.getMin();
  333.         if ((max != null || min != null) && formField.getType() != FormField.Type.list_multi) {
  334.             throw new ValidationConsistencyException(
  335.                             "Field type is not of type 'list-multi' while a 'list-range' is defined.");
  336.         }
  337.     }

  338.     /**
  339.      * @param formField
  340.      * @param method
  341.      */
  342.     protected void checkNonMultiConsistency(FormField formField, String method) {
  343.         checkListRangeConsistency(formField);
  344.         if (formField.getType() != null) {
  345.             switch (formField.getType()) {
  346.             case hidden:
  347.             case jid_multi:
  348.             case list_multi:
  349.             case text_multi:
  350.                 throw new ValidationConsistencyException(String.format(
  351.                                 "Field type '%1$s' is not consistent with validation method '%2$s'.",
  352.                                 formField.getType(), method));
  353.             default:
  354.                 break;
  355.             }
  356.         }
  357.     }
  358. }