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 * @param listRange the listRange to set 102 */ 103 public void setListRange(ListRange listRange) { 104 this.listRange = listRange; 105 } 106 107 /** 108 * @return the listRange 109 */ 110 public ListRange getListRange() { 111 return listRange; 112 } 113 114 /** 115 * Check if this element is consistent according to the business rules in XEP=0122 116 * 117 * @param formField 118 */ 119 public abstract void checkConsistency(FormField formField); 120 121 /** 122 * Validation only against the datatype itself. Indicates that the value(s) should simply match the field type and 123 * datatype constraints. 124 * 125 * @see ValidateElement 126 */ 127 public static class BasicValidateElement extends ValidateElement { 128 129 public static final String METHOD = "basic"; 130 131 /** 132 * @param dataType 133 * @see #getDatatype() 134 */ 135 public BasicValidateElement(String dataType) { 136 super(dataType); 137 } 138 139 @Override 140 protected void appendXML(XmlStringBuilder buf) { 141 buf.emptyElement(METHOD); 142 } 143 144 public void checkConsistency(FormField formField) { 145 checkListRangeConsistency(formField); 146 if (formField.getType() != null) { 147 switch (formField.getType()) { 148 case hidden: 149 case jid_multi: 150 case jid_single: 151 throw new ValidationConsistencyException(String.format( 152 "Field type '%1$s' is not consistent with validation method '%2$s'.", 153 formField.getType(), BasicValidateElement.METHOD)); 154 default: 155 break; 156 } 157 } 158 } 159 160 } 161 162 /** 163 * For "list-single" or "list-multi", indicates that the user may enter a custom value (matching the datatype 164 * constraints) or choose from the predefined values. 165 * 166 * @see ValidateElement 167 */ 168 public static class OpenValidateElement extends ValidateElement { 169 170 public static final String METHOD = "open"; 171 172 /** 173 * @param dataType 174 * @see #getDatatype() 175 */ 176 public OpenValidateElement(String dataType) { 177 super(dataType); 178 } 179 180 @Override 181 protected void appendXML(XmlStringBuilder buf) { 182 buf.emptyElement(METHOD); 183 } 184 185 public void checkConsistency(FormField 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 201 /** 202 * Indicate that the value should fall within a certain range. 203 * 204 * @see ValidateElement 205 */ 206 public static class RangeValidateElement extends ValidateElement { 207 208 public static final String METHOD = "range"; 209 private final String min; 210 private final String max; 211 212 /** 213 * @param dataType 214 * @param min the minimum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use. 215 * @param max the maximum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use. 216 * @see #getDatatype() 217 * 218 */ 219 public RangeValidateElement(String dataType, String min, String max) { 220 super(dataType); 221 this.min = min; 222 this.max = max; 223 } 224 225 @Override 226 protected void appendXML(XmlStringBuilder buf) { 227 buf.halfOpenElement(METHOD); 228 buf.optAttribute("min", getMin()); 229 buf.optAttribute("max", getMax()); 230 buf.closeEmptyElement(); 231 } 232 233 /** 234 * The 'min' attribute specifies the minimum allowable value. 235 * 236 * @return the minimum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use. 237 */ 238 public String getMin() { 239 return min; 240 } 241 242 /** 243 * The 'max' attribute specifies the maximum allowable value. 244 * 245 * @return the maximum allowable value. This attribute is OPTIONAL. The value depends on the datatype in use. 246 */ 247 public String getMax() { 248 return max; 249 } 250 251 public void checkConsistency(FormField formField) { 252 checkNonMultiConsistency(formField, METHOD); 253 if (getDatatype().equals(ValidateElement.DATATYPE_XS_STRING)) { 254 throw new ValidationConsistencyException(String.format( 255 "Field data type '%1$s' is not consistent with validation method '%2$s'.", 256 getDatatype(), RangeValidateElement.METHOD)); 257 } 258 } 259 260 } 261 262 /** 263 * Indicates that the value should be restricted to a regular expression. The regular expression must be that 264 * defined for <a href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1501344"> POSIX extended regular 265 * expressions </a> including support for <a 266 * href="http://www.xmpp.org/extensions/xep-0122.html#nt-idp1502496">Unicode</a>. 267 * 268 * @see ValidateElement 269 */ 270 public static class RegexValidateElement extends ValidateElement { 271 272 public static final String METHOD = "regex"; 273 private final String regex; 274 275 /** 276 * @param dataType 277 * @param regex 278 * @see #getDatatype() 279 */ 280 public RegexValidateElement(String dataType, String regex) { 281 super(dataType); 282 this.regex = regex; 283 } 284 285 /** 286 * the expression is that defined for POSIX extended regular expressions, including support for Unicode. 287 * 288 * @return the regex 289 */ 290 public String getRegex() { 291 return regex; 292 } 293 294 @Override 295 protected void appendXML(XmlStringBuilder buf) { 296 buf.element("regex", getRegex()); 297 } 298 299 public void checkConsistency(FormField formField) { 300 checkNonMultiConsistency(formField, METHOD); 301 } 302 303 } 304 305 /** 306 * This element indicates for "list-multi", that a minimum and maximum number of options should be selected and/or 307 * entered. 308 */ 309 public static class ListRange implements NamedElement { 310 311 public static final String ELEMENT = "list-range"; 312 private final Long min; 313 private final Long max; 314 315 /** 316 * The 'max' attribute specifies the maximum allowable number of selected/entered values. The 'min' attribute 317 * specifies the minimum allowable number of selected/entered values. Both attributes are optional, but at 318 * least one must bet set, and the value must be within the range of a unsigned 32-bit integer. 319 * 320 * @param min 321 * @param max 322 */ 323 public ListRange(Long min, Long max) { 324 if (min != null) { 325 NumberUtil.checkIfInUInt32Range(min); 326 } 327 if (max != null) { 328 NumberUtil.checkIfInUInt32Range(max); 329 } 330 if (max == null && min == null) { 331 throw new IllegalArgumentException("Either min or max must be given"); 332 } 333 this.min = min; 334 this.max = max; 335 } 336 337 public XmlStringBuilder toXML() { 338 XmlStringBuilder buf = new XmlStringBuilder(this); 339 buf.optLongAttribute("min", getMin()); 340 buf.optLongAttribute("max", getMax()); 341 buf.closeEmptyElement(); 342 return buf; 343 } 344 345 @Override 346 public String getElementName() { 347 return ELEMENT; 348 } 349 350 /** 351 * The minimum allowable number of selected/entered values. 352 * 353 * @return a positive integer, can be null 354 */ 355 public Long getMin() { 356 return min; 357 } 358 359 /** 360 * The maximum allowable number of selected/entered values. 361 * 362 * @return a positive integer, can be null 363 */ 364 public Long getMax() { 365 return max; 366 } 367 368 } 369 370 /** 371 * The <list-range/> element SHOULD be included only when the <field/> is of type "list-multi" and SHOULD be ignored 372 * otherwise. 373 * 374 * @param formField 375 */ 376 protected void checkListRangeConsistency(FormField formField) { 377 ListRange listRange = getListRange(); 378 if (listRange == null) { 379 return; 380 } 381 382 Long max = listRange.getMax(); 383 Long min = listRange.getMin(); 384 if ((max != null || min != null) && formField.getType() != FormField.Type.list_multi) { 385 throw new ValidationConsistencyException( 386 "Field type is not of type 'list-multi' while a 'list-range' is defined."); 387 } 388 } 389 390 /** 391 * @param formField 392 * @param method 393 */ 394 protected void checkNonMultiConsistency(FormField formField, String method) { 395 checkListRangeConsistency(formField); 396 if (formField.getType() != null) { 397 switch (formField.getType()) { 398 case hidden: 399 case jid_multi: 400 case list_multi: 401 case text_multi: 402 throw new ValidationConsistencyException(String.format( 403 "Field type '%1$s' is not consistent with validation method '%2$s'.", 404 formField.getType(), method)); 405 default: 406 break; 407 } 408 } 409 } 410} 411