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