001/** 002 * 003 * Copyright 2014 Anno van Vliet 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 */ 017package org.jivesoftware.smackx.xdatavalidation.packet; 018 019import org.jivesoftware.smack.packet.ExtensionElement; 020import org.jivesoftware.smack.packet.NamedElement; 021import org.jivesoftware.smack.util.NumberUtil; 022import org.jivesoftware.smack.util.StringUtils; 023import org.jivesoftware.smack.util.XmlStringBuilder; 024 025import org.jivesoftware.smackx.xdata.FormField; 026import org.jivesoftware.smackx.xdata.packet.DataForm; 027import org.jivesoftware.smackx.xdatavalidation.ValidationConsistencyException; 028 029/** 030 * DataValidation Extension according to XEP-0122: Data Forms Validation. This specification defines a 031 * backwards-compatible extension to the XMPP Data Forms protocol that enables applications to specify additional 032 * validation guidelines related to a {@link FormField} in a {@link DataForm}, such as validation of standard XML 033 * datatypes, application-specific datatypes, value ranges, and regular expressions. 034 * 035 * @author Anno van Vliet 036 */ 037public abstract class ValidateElement implements ExtensionElement { 038 039 public static final String DATATYPE_XS_STRING = "xs:string"; 040 public static final String ELEMENT = "validate"; 041 public static final String NAMESPACE = "http://jabber.org/protocol/xdata-validate"; 042 043 private final String datatype; 044 045 private ListRange listRange; 046 047 /** 048 * The 'datatype' attribute specifies the datatype. This attribute is OPTIONAL, and when not specified, defaults to 049 * "xs:string". 050 * 051 * @param datatype the data type of any value contained within the {@link FormField} element. 052 */ 053 private ValidateElement(String datatype) { 054 this.datatype = StringUtils.isNotEmpty(datatype) ? datatype : null; 055 } 056 057 /** 058 * Specifies the data type of any value contained within the {@link FormField} element. It MUST meet one of the 059 * following conditions: 060 * <ul> 061 * <li>Start with "xs:", and be one of the "built-in" datatypes defined in XML Schema Part 2 <a 062 * href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1476016">[2]</a></li> 063 * <li>Start with a prefix registered with the XMPP Registrar <a 064 * href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1478544">[3]</a></li> 065 * <li>Start with "x:", and specify a user-defined datatype <a 066 * href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1477360">[4]</a></li> 067 * </ul> 068 * 069 * @return the datatype 070 */ 071 public String getDatatype() { 072 return datatype != null ? datatype : DATATYPE_XS_STRING; 073 } 074 075 @Override 076 public String getElementName() { 077 return ELEMENT; 078 } 079 080 @Override 081 public String getNamespace() { 082 return NAMESPACE; 083 } 084 085 @Override 086 public XmlStringBuilder toXML(String enclosingNamespace) { 087 XmlStringBuilder buf = new XmlStringBuilder(this); 088 buf.optAttribute("datatype", datatype); 089 buf.rightAngleBracket(); 090 appendXML(buf); 091 buf.optAppend(getListRange()); 092 buf.closeElement(this); 093 return buf; 094 } 095 096 /** 097 * @param buf 098 */ 099 protected abstract void appendXML(XmlStringBuilder buf); 100 101 /** 102 * Set list range. 103 * @param listRange the listRange to set 104 */ 105 public void setListRange(ListRange listRange) { 106 this.listRange = listRange; 107 } 108 109 /** 110 * Get list range. 111 * @return the listRange 112 */ 113 public ListRange getListRange() { 114 return listRange; 115 } 116 117 /** 118 * Check if this element is consistent according to the business rules in XEP=0122. 119 * 120 * @param formField 121 */ 122 public abstract void checkConsistency(FormField formField); 123 124 /** 125 * Validation only against the datatype itself. Indicates that the value(s) should simply match the field type and 126 * datatype constraints. 127 * 128 * @see ValidateElement 129 */ 130 public static class BasicValidateElement extends ValidateElement { 131 132 public static final String METHOD = "basic"; 133 134 /** 135 * Basic validate element constructor. 136 * @param dataType 137 * @see #getDatatype() 138 */ 139 public BasicValidateElement(String dataType) { 140 super(dataType); 141 } 142 143 @Override 144 protected void appendXML(XmlStringBuilder buf) { 145 buf.emptyElement(METHOD); 146 } 147 148 @Override 149 public void checkConsistency(FormField formField) { 150 checkListRangeConsistency(formField); 151 if (formField.getType() != null) { 152 switch (formField.getType()) { 153 case hidden: 154 case jid_multi: 155 case jid_single: 156 throw new ValidationConsistencyException(String.format( 157 "Field type '%1$s' is not consistent with validation method '%2$s'.", 158 formField.getType(), BasicValidateElement.METHOD)); 159 default: 160 break; 161 } 162 } 163 } 164 165 } 166 167 /** 168 * For "list-single" or "list-multi", indicates that the user may enter a custom value (matching the datatype 169 * constraints) or choose from the predefined values. 170 * 171 * @see ValidateElement 172 */ 173 public static class OpenValidateElement extends ValidateElement { 174 175 public static final String METHOD = "open"; 176 177 /** 178 * Open validate element constructor. 179 * @param dataType 180 * @see #getDatatype() 181 */ 182 public OpenValidateElement(String dataType) { 183 super(dataType); 184 } 185 186 @Override 187 protected void appendXML(XmlStringBuilder buf) { 188 buf.emptyElement(METHOD); 189 } 190 191 @Override 192 public void checkConsistency(FormField formField) { 193 checkListRangeConsistency(formField); 194 if (formField.getType() != null) { 195 switch (formField.getType()) { 196 case hidden: 197 throw new ValidationConsistencyException(String.format( 198 "Field type '%1$s' is not consistent with validation method '%2$s'.", 199 formField.getType(), OpenValidateElement.METHOD)); 200 default: 201 break; 202 } 203 } 204 } 205 206 } 207 208 /** 209 * Indicate that the value should fall within a certain range. 210 * 211 * @see ValidateElement 212 */ 213 public static class RangeValidateElement extends ValidateElement { 214 215 public static final String METHOD = "range"; 216 private final String min; 217 private final String max; 218 219 /** 220 * Range validate element constructor. 221 * @param dataType 222 * @param min the minimum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use. 223 * @param max the maximum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use. 224 * @see #getDatatype() 225 * 226 */ 227 public RangeValidateElement(String dataType, String min, String max) { 228 super(dataType); 229 this.min = min; 230 this.max = max; 231 } 232 233 @Override 234 protected void appendXML(XmlStringBuilder buf) { 235 buf.halfOpenElement(METHOD); 236 buf.optAttribute("min", getMin()); 237 buf.optAttribute("max", getMax()); 238 buf.closeEmptyElement(); 239 } 240 241 /** 242 * The 'min' attribute specifies the minimum allowable value. 243 * 244 * @return the minimum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use. 245 */ 246 public String getMin() { 247 return min; 248 } 249 250 /** 251 * The 'max' attribute specifies the maximum allowable value. 252 * 253 * @return the maximum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use. 254 */ 255 public String getMax() { 256 return max; 257 } 258 259 @Override 260 public void checkConsistency(FormField formField) { 261 checkNonMultiConsistency(formField, METHOD); 262 if (getDatatype().equals(ValidateElement.DATATYPE_XS_STRING)) { 263 throw new ValidationConsistencyException(String.format( 264 "Field data type '%1$s' is not consistent with validation method '%2$s'.", 265 getDatatype(), RangeValidateElement.METHOD)); 266 } 267 } 268 269 } 270 271 /** 272 * Indicates that the value should be restricted to a regular expression. The regular expression must be that 273 * defined for <a href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1501344"> POSIX extended regular 274 * expressions </a> including support for <a 275 * href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1502496">Unicode</a>. 276 * 277 * @see ValidateElement 278 */ 279 public static class RegexValidateElement extends ValidateElement { 280 281 public static final String METHOD = "regex"; 282 private final String regex; 283 284 /** 285 * Regex validate element. 286 * @param dataType 287 * @param regex 288 * @see #getDatatype() 289 */ 290 public RegexValidateElement(String dataType, String regex) { 291 super(dataType); 292 this.regex = regex; 293 } 294 295 /** 296 * the expression is that defined for POSIX extended regular expressions, including support for Unicode. 297 * 298 * @return the regex 299 */ 300 public String getRegex() { 301 return regex; 302 } 303 304 @Override 305 protected void appendXML(XmlStringBuilder buf) { 306 buf.element("regex", getRegex()); 307 } 308 309 @Override 310 public void checkConsistency(FormField formField) { 311 checkNonMultiConsistency(formField, METHOD); 312 } 313 314 } 315 316 /** 317 * This element indicates for "list-multi", that a minimum and maximum number of options should be selected and/or 318 * entered. 319 */ 320 public static class ListRange implements NamedElement { 321 322 public static final String ELEMENT = "list-range"; 323 private final Long min; 324 private final Long max; 325 326 /** 327 * The 'max' attribute specifies the maximum allowable number of selected/entered values. The 'min' attribute 328 * specifies the minimum allowable number of selected/entered values. Both attributes are optional, but at 329 * least one must bet set, and the value must be within the range of a unsigned 32-bit integer. 330 * 331 * @param min 332 * @param max 333 */ 334 public ListRange(Long min, Long max) { 335 if (min != null) { 336 NumberUtil.checkIfInUInt32Range(min); 337 } 338 if (max != null) { 339 NumberUtil.checkIfInUInt32Range(max); 340 } 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 348 @Override 349 public XmlStringBuilder toXML(String enclosingNamespace) { 350 XmlStringBuilder buf = new XmlStringBuilder(this); 351 buf.optLongAttribute("min", getMin()); 352 buf.optLongAttribute("max", getMax()); 353 buf.closeEmptyElement(); 354 return buf; 355 } 356 357 @Override 358 public String getElementName() { 359 return ELEMENT; 360 } 361 362 /** 363 * The minimum allowable number of selected/entered values. 364 * 365 * @return a positive integer, can be null 366 */ 367 public Long getMin() { 368 return min; 369 } 370 371 /** 372 * The maximum allowable number of selected/entered values. 373 * 374 * @return a positive integer, can be null 375 */ 376 public Long getMax() { 377 return max; 378 } 379 380 } 381 382 /** 383 * The >list-range/< element SHOULD be included only when the <field/> is of type "list-multi" and SHOULD be ignored 384 * otherwise. 385 * 386 * @param formField 387 */ 388 protected void checkListRangeConsistency(FormField formField) { 389 ListRange listRange = getListRange(); 390 if (listRange == null) { 391 return; 392 } 393 394 Long max = listRange.getMax(); 395 Long min = listRange.getMin(); 396 if ((max != null || min != null) && formField.getType() != FormField.Type.list_multi) { 397 throw new ValidationConsistencyException( 398 "Field type is not of type 'list-multi' while a 'list-range' is defined."); 399 } 400 } 401 402 /** 403 * @param formField 404 * @param method 405 */ 406 protected void checkNonMultiConsistency(FormField formField, String method) { 407 checkListRangeConsistency(formField); 408 if (formField.getType() != null) { 409 switch (formField.getType()) { 410 case hidden: 411 case jid_multi: 412 case list_multi: 413 case text_multi: 414 throw new ValidationConsistencyException(String.format( 415 "Field type '%1$s' is not consistent with validation method '%2$s'.", 416 formField.getType(), method)); 417 default: 418 break; 419 } 420 } 421 } 422} 423