001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.httppart; 014 015import static java.util.Collections.*; 016import static org.apache.juneau.httppart.HttpPartDataType.*; 017import static org.apache.juneau.httppart.HttpPartFormat.*; 018import static org.apache.juneau.internal.StringUtils.*; 019 020import java.lang.annotation.*; 021import java.lang.reflect.*; 022import java.math.*; 023import java.util.*; 024import java.util.concurrent.atomic.*; 025import java.util.regex.*; 026 027import org.apache.juneau.*; 028import org.apache.juneau.collections.*; 029import org.apache.juneau.http.annotation.*; 030import org.apache.juneau.internal.*; 031import org.apache.juneau.parser.*; 032import org.apache.juneau.reflect.*; 033 034/** 035 * Represents an OpenAPI schema definition. 036 * 037 * <p> 038 * The schema definition can be applied to any HTTP parts such as bodies, headers, query/form parameters, and URL path parts. 039 * <br>The API is generic enough to apply to any path part although some attributes may only applicable for certain parts. 040 * 041 * <p> 042 * Schema objects are created via builders instantiated through the {@link #create()} method. 043 * 044 * <p> 045 * This class is thread safe and reusable. 046 * 047 * <ul class='seealso'> 048 * <li class='link'>{@doc OpenApiDetails} 049 * </ul> 050 */ 051public class HttpPartSchema { 052 053 //------------------------------------------------------------------------------------------------------------------- 054 // Predefined instances 055 //------------------------------------------------------------------------------------------------------------------- 056 057 /** Reusable instance of this object, all default settings. */ 058 public static final HttpPartSchema DEFAULT = HttpPartSchema.create().allowEmptyValue(true).build(); 059 060 /** Boolean type */ 061 public static final HttpPartSchema T_BOOLEAN = HttpPartSchema.tBoolean().build(); 062 063 /** File type */ 064 public static final HttpPartSchema T_FILE = HttpPartSchema.tFile().build(); 065 066 /** Integer type */ 067 public static final HttpPartSchema T_INTEGER = HttpPartSchema.tInteger().build(); 068 069 /** Int32 type */ 070 public static final HttpPartSchema T_INT32 = HttpPartSchema.tInt32().build(); 071 072 /** Int64 type */ 073 public static final HttpPartSchema T_INT64 = HttpPartSchema.tInt64().build(); 074 075 /** No type */ 076 public static final HttpPartSchema T_NONE = HttpPartSchema.tNone().build(); 077 078 /** Number type */ 079 public static final HttpPartSchema T_NUMBER = HttpPartSchema.tNumber().build(); 080 081 /** Float type */ 082 public static final HttpPartSchema T_FLOAT = HttpPartSchema.tFloat().build(); 083 084 /** Double type */ 085 public static final HttpPartSchema T_DOUBLE = HttpPartSchema.tDouble().build(); 086 087 /** String type */ 088 public static final HttpPartSchema T_STRING = HttpPartSchema.tString().build(); 089 090 /** Byte type */ 091 public static final HttpPartSchema T_BYTE = HttpPartSchema.tByte().build(); 092 093 /** Binary type */ 094 public static final HttpPartSchema T_BINARY = HttpPartSchema.tBinary().build(); 095 096 /** Spaced binary type */ 097 public static final HttpPartSchema T_BINARY_SPACED = HttpPartSchema.tBinarySpaced().build(); 098 099 /** Date type */ 100 public static final HttpPartSchema T_DATE = HttpPartSchema.tDate().build(); 101 102 /** Date-time type */ 103 public static final HttpPartSchema T_DATETIME = HttpPartSchema.tDateTime().build(); 104 105 /** UON-formated simple type */ 106 public static final HttpPartSchema T_UON = HttpPartSchema.tUon().build(); 107 108 /** Array type */ 109 public static final HttpPartSchema T_ARRAY = HttpPartSchema.tArray().build(); 110 111 /** Comma-delimited array type */ 112 public static final HttpPartSchema T_ARRAY_CSV = HttpPartSchema.tArrayCsv().build(); 113 114 /** Pipe-delimited array type */ 115 public static final HttpPartSchema T_ARRAY_PIPES = HttpPartSchema.tArrayPipes().build(); 116 117 /** Space-delimited array type */ 118 public static final HttpPartSchema T_ARRAY_SSV = HttpPartSchema.tArraySsv().build(); 119 120 /** Tab-delimited array type */ 121 public static final HttpPartSchema T_ARRAY_TSV = HttpPartSchema.tArrayTsv().build(); 122 123 /** UON-formatted array type */ 124 public static final HttpPartSchema T_ARRAY_UON = HttpPartSchema.tArrayUon().build(); 125 126 /** Multi-part array type */ 127 public static final HttpPartSchema T_ARRAY_MULTI = HttpPartSchema.tArrayMulti().build(); 128 129 /** Object type */ 130 public static final HttpPartSchema T_OBJECT = HttpPartSchema.tObject().build(); 131 132 /** Comma-delimited object type */ 133 public static final HttpPartSchema T_OBJECT_CSV = HttpPartSchema.tObjectCsv().build(); 134 135 /** Pipe-delimited object type */ 136 public static final HttpPartSchema T_OBJECT_PIPES = HttpPartSchema.tObjectPipes().build(); 137 138 /** Space-delimited object type */ 139 public static final HttpPartSchema T_OBJECT_SSV = HttpPartSchema.tObjectSsv().build(); 140 141 /** Tab-delimited object type */ 142 public static final HttpPartSchema T_OBJECT_TSV = HttpPartSchema.tObjectTsv().build(); 143 144 /** UON-formated object type */ 145 public static final HttpPartSchema T_OBJECT_UON = HttpPartSchema.tObjectUon().build(); 146 147 148 final String name; 149 final Set<Integer> codes; 150 final String _default; 151 final Set<String> _enum; 152 final Map<String,HttpPartSchema> properties; 153 final boolean allowEmptyValue, exclusiveMaximum, exclusiveMinimum, required, uniqueItems, skipIfEmpty; 154 final HttpPartCollectionFormat collectionFormat; 155 final HttpPartDataType type; 156 final HttpPartFormat format; 157 final Pattern pattern; 158 final HttpPartSchema items, additionalProperties; 159 final Number maximum, minimum, multipleOf; 160 final Long maxLength, minLength, maxItems, minItems, maxProperties, minProperties; 161 final Class<? extends HttpPartParser> parser; 162 final Class<? extends HttpPartSerializer> serializer; 163 final ClassMeta<?> parsedType; 164 165 /** 166 * Instantiates a new builder for this object. 167 * 168 * @return A new builder for this object. 169 */ 170 public static HttpPartSchemaBuilder create() { 171 return new HttpPartSchemaBuilder(); 172 } 173 174 /** 175 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>BOOLEAN</jsf>)</c>. 176 * 177 * @return A new builder for this object. 178 */ 179 public static HttpPartSchemaBuilder tBoolean() { 180 return create().tBoolean(); 181 } 182 183 /** 184 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>FILE</jsf>)</c>. 185 * 186 * @return A new builder for this object. 187 */ 188 public static HttpPartSchemaBuilder tFile() { 189 return create().tFile(); 190 } 191 192 /** 193 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>)</c>. 194 * 195 * @return A new builder for this object. 196 */ 197 public static HttpPartSchemaBuilder tInteger() { 198 return create().tInteger(); 199 } 200 201 /** 202 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>).format(HttpPartFormat.<jsf>INT32</jsf>)</c>. 203 * 204 * @return A new builder for this object. 205 */ 206 public static HttpPartSchemaBuilder tInt32() { 207 return create().tInteger().fInt32(); 208 } 209 210 /** 211 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>INTEGER</jsf>).format(HttpPartFormat.<jsf>INT64</jsf>)</c>. 212 * 213 * @return A new builder for this object. 214 */ 215 public static HttpPartSchemaBuilder tInt64() { 216 return create().tInteger().fInt64(); 217 } 218 219 /** 220 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NONE</jsf>)</c>. 221 * 222 * @return A new builder for this object. 223 */ 224 public static HttpPartSchemaBuilder tNone() { 225 return create().tNone(); 226 } 227 228 /** 229 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>)</c>. 230 * 231 * @return A new builder for this object. 232 */ 233 public static HttpPartSchemaBuilder tNumber() { 234 return create().tNumber(); 235 } 236 237 /** 238 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>).format(HttpPartFormat.<jsf>FLOAT</jsf>)</c>. 239 * 240 * @return A new builder for this object. 241 */ 242 public static HttpPartSchemaBuilder tFloat() { 243 return create().tNumber().fFloat(); 244 } 245 246 /** 247 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>NUMBER</jsf>).format(HttpPartFormat.<jsf>DOUBLE</jsf>)</c>. 248 * 249 * @return A new builder for this object. 250 */ 251 public static HttpPartSchemaBuilder tDouble() { 252 return create().tNumber().fDouble(); 253 } 254 255 /** 256 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>)</c>. 257 * 258 * @return A new builder for this object. 259 */ 260 public static HttpPartSchemaBuilder tString() { 261 return create().tString(); 262 } 263 264 /** 265 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BYTE</jsf>)</c>. 266 * 267 * @return A new builder for this object. 268 */ 269 public static HttpPartSchemaBuilder tByte() { 270 return create().tString().fByte(); 271 } 272 273 /** 274 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BINARY</jsf>)</c>. 275 * 276 * @return A new builder for this object. 277 */ 278 public static HttpPartSchemaBuilder tBinary() { 279 return create().tString().fBinary(); 280 } 281 282 /** 283 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>BINARY_SPACED</jsf>)</c>. 284 * 285 * @return A new builder for this object. 286 */ 287 public static HttpPartSchemaBuilder tBinarySpaced() { 288 return create().tString().fBinarySpaced(); 289 } 290 291 /** 292 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>DATE</jsf>)</c>. 293 * 294 * @return A new builder for this object. 295 */ 296 public static HttpPartSchemaBuilder tDate() { 297 return create().tString().fDate(); 298 } 299 300 /** 301 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>DATE_TIME</jsf>)</c>. 302 * 303 * @return A new builder for this object. 304 */ 305 public static HttpPartSchemaBuilder tDateTime() { 306 return create().tString().fDateTime(); 307 } 308 309 /** 310 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>STRING</jsf>).format(HttpPartFormat.<jsf>UON</jsf>)</c>. 311 * 312 * @return A new builder for this object. 313 */ 314 public static HttpPartSchemaBuilder tUon() { 315 return create().tString().fUon(); 316 } 317 318 /** 319 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>)</c>. 320 * 321 * @return A new builder for this object. 322 */ 323 public static HttpPartSchemaBuilder tArray() { 324 return create().tArray(); 325 } 326 327 /** 328 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).items(items)</c>. 329 * 330 * @param items The schema of the array items. 331 * @return A new builder for this object. 332 */ 333 public static HttpPartSchemaBuilder tArray(HttpPartSchemaBuilder items) { 334 return create().tArray().items(items); 335 } 336 337 /** 338 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>)</c>. 339 * 340 * @return A new builder for this object. 341 */ 342 public static HttpPartSchemaBuilder tArrayCsv() { 343 return create().tArray().cfCsv(); 344 } 345 346 /** 347 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>).items(items)</c>. 348 * 349 * @param items The schema of the array items. 350 * @return A new builder for this object. 351 */ 352 public static HttpPartSchemaBuilder tArrayCsv(HttpPartSchemaBuilder items) { 353 return create().tArray().cfCsv().items(items); 354 } 355 356 /** 357 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>)</c>. 358 * 359 * @return A new builder for this object. 360 */ 361 public static HttpPartSchemaBuilder tArrayPipes() { 362 return create().tArray().cfPipes(); 363 } 364 365 /** 366 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>).items(items)</c>. 367 * 368 * @param items The schema of the array items. 369 * @return A new builder for this object. 370 */ 371 public static HttpPartSchemaBuilder tArrayPipes(HttpPartSchemaBuilder items) { 372 return create().tArray().cfPipes().items(items); 373 } 374 375 /** 376 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>)</c>. 377 * 378 * @return A new builder for this object. 379 */ 380 public static HttpPartSchemaBuilder tArraySsv() { 381 return create().tArray().cfSsv(); 382 } 383 384 /** 385 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>).items(items)</c>. 386 * 387 * @param items The schema of the array items. 388 * @return A new builder for this object. 389 */ 390 public static HttpPartSchemaBuilder tArraySsv(HttpPartSchemaBuilder items) { 391 return create().tArray().cfSsv().items(items); 392 } 393 394 /** 395 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>)</c>. 396 * 397 * @return A new builder for this object. 398 */ 399 public static HttpPartSchemaBuilder tArrayTsv() { 400 return create().tArray().cfTsv(); 401 } 402 403 /** 404 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>).items(items)</c>. 405 * 406 * @param items The schema of the array items. 407 * @return A new builder for this object. 408 */ 409 public static HttpPartSchemaBuilder tArrayTsv(HttpPartSchemaBuilder items) { 410 return create().tArray().cfTsv().items(items); 411 } 412 413 /** 414 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UONC</jsf>)</c>. 415 * 416 * @return A new builder for this object. 417 */ 418 public static HttpPartSchemaBuilder tArrayUon() { 419 return create().tArray().cfUon(); 420 } 421 422 /** 423 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UONC</jsf>).items(items)</c>. 424 * 425 * @param items The schema of the array items. 426 * @return A new builder for this object. 427 */ 428 public static HttpPartSchemaBuilder tArrayUon(HttpPartSchemaBuilder items) { 429 return create().tArray().cfUon().items(items); 430 } 431 432 /** 433 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>MULTI</jsf>)</c>. 434 * 435 * @return A new builder for this object. 436 */ 437 public static HttpPartSchemaBuilder tArrayMulti() { 438 return create().tArray().cfMulti(); 439 } 440 441 /** 442 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>ARRAY</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>MULTI</jsf>).items(items)</c>. 443 * 444 * @param items The schema of the array items. 445 * @return A new builder for this object. 446 */ 447 public static HttpPartSchemaBuilder tArrayMulti(HttpPartSchemaBuilder items) { 448 return create().tArray().cfMulti().items(items); 449 } 450 451 /** 452 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>)</c>. 453 * 454 * @return A new builder for this object. 455 */ 456 public static HttpPartSchemaBuilder tObject() { 457 return create().tObject(); 458 } 459 460 /** 461 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>CSV</jsf>)</c>. 462 * 463 * @return A new builder for this object. 464 */ 465 public static HttpPartSchemaBuilder tObjectCsv() { 466 return create().tObject().cfCsv(); 467 } 468 469 /** 470 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>PIPES</jsf>)</c>. 471 * 472 * @return A new builder for this object. 473 */ 474 public static HttpPartSchemaBuilder tObjectPipes() { 475 return create().tObject().cfPipes(); 476 } 477 478 /** 479 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>SSV</jsf>)</c>. 480 * 481 * @return A new builder for this object. 482 */ 483 public static HttpPartSchemaBuilder tObjectSsv() { 484 return create().tObject().cfSsv(); 485 } 486 487 /** 488 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>TSV</jsf>)</c>. 489 * 490 * @return A new builder for this object. 491 */ 492 public static HttpPartSchemaBuilder tObjectTsv() { 493 return create().tObject().cfTsv(); 494 } 495 496 /** 497 * Shortcut for <c><jsm>create</jsm>().type(HttpPartDataType.<jsf>OBJECT</jsf>).collectionFormat(HttpPartCollectionFormat.<jsf>UON</jsf>)</c>. 498 * 499 * @return A new builder for this object. 500 */ 501 public static HttpPartSchemaBuilder tObjectUon() { 502 return create().tObject().cfUon(); 503 } 504 505 /** 506 * Finds the schema information for the specified method parameter. 507 * 508 * <p> 509 * This method will gather all the schema information from the annotations at the following locations: 510 * <ul> 511 * <li>The method parameter. 512 * <li>The method parameter class. 513 * <li>The method parameter parent classes and interfaces. 514 * </ul> 515 * 516 * @param c 517 * The annotation to look for. 518 * <br>Valid values: 519 * <ul> 520 * <li>{@link Body} 521 * <li>{@link Header} 522 * <li>{@link Query} 523 * <li>{@link FormData} 524 * <li>{@link Path} 525 * <li>{@link Response} 526 * <li>{@link ResponseHeader} 527 * <li>{@link ResponseBody} 528 * <li>{@link HasQuery} 529 * <li>{@link HasFormData} 530 * </ul> 531 * @param mpi The Java method parameter. 532 * @return The schema information about the parameter. 533 */ 534 public static HttpPartSchema create(Class<? extends Annotation> c, ParamInfo mpi) { 535 return create().apply(c, mpi).build(); 536 } 537 538 /** 539 * Finds the schema information for the specified method return. 540 * 541 * <p> 542 * This method will gather all the schema information from the annotations at the following locations: 543 * <ul> 544 * <li>The method. 545 * <li>The method return class. 546 * <li>The method return parent classes and interfaces. 547 * </ul> 548 * 549 * @param c 550 * The annotation to look for. 551 * <br>Valid values: 552 * <ul> 553 * <li>{@link Body} 554 * <li>{@link Header} 555 * <li>{@link Query} 556 * <li>{@link FormData} 557 * <li>{@link Path} 558 * <li>{@link Response} 559 * <li>{@link ResponseHeader} 560 * <li>{@link HasQuery} 561 * <li>{@link HasFormData} 562 * </ul> 563 * @param m 564 * The Java method with the return type being checked. 565 * @return The schema information about the parameter. 566 */ 567 public static HttpPartSchema create(Class<? extends Annotation> c, Method m) { 568 return create().apply(c, m).build(); 569 } 570 571 /** 572 * Finds the schema information for the specified class. 573 * 574 * <p> 575 * This method will gather all the schema information from the annotations on the class and all parent classes/interfaces. 576 * 577 * @param c 578 * The annotation to look for. 579 * <br>Valid values: 580 * <ul> 581 * <li>{@link Body} 582 * <li>{@link Header} 583 * <li>{@link Query} 584 * <li>{@link FormData} 585 * <li>{@link Path} 586 * <li>{@link Response} 587 * <li>{@link ResponseHeader} 588 * <li>{@link HasQuery} 589 * <li>{@link HasFormData} 590 * </ul> 591 * @param t 592 * The class containing the parameter. 593 * @return The schema information about the parameter. 594 */ 595 public static HttpPartSchema create(Class<? extends Annotation> c, java.lang.reflect.Type t) { 596 return create().apply(c, t).build(); 597 } 598 599 /** 600 * Shortcut for calling <c>create().type(type);</c> 601 * 602 * @param type The schema type value. 603 * @return A new builder. 604 */ 605 public static HttpPartSchemaBuilder create(String type) { 606 return create().type(type); 607 } 608 609 /** 610 * Shortcut for calling <c>create().type(type).format(format);</c> 611 * 612 * @param type The schema type value. 613 * @param format The schema format value. 614 * @return A new builder. 615 */ 616 public static HttpPartSchemaBuilder create(String type, String format) { 617 return create().type(type).format(format); 618 } 619 620 /** 621 * Finds the schema information on the specified annotation. 622 * 623 * @param a 624 * The annotation to find the schema information on.. 625 * @return The schema information found on the annotation. 626 */ 627 public static HttpPartSchema create(Annotation a) { 628 return create().apply(a).build(); 629 } 630 631 /** 632 * Finds the schema information on the specified annotation. 633 * 634 * @param a 635 * The annotation to find the schema information on.. 636 * @param defaultName The default part name if not specified on the annotation. 637 * @return The schema information found on the annotation. 638 */ 639 public static HttpPartSchema create(Annotation a, String defaultName) { 640 return create().name(defaultName).apply(a).build(); 641 } 642 643 HttpPartSchema(HttpPartSchemaBuilder b) { 644 this.name = b.name; 645 this.codes = copy(b.codes); 646 this._default = b._default; 647 this._enum = copy(b._enum); 648 this.properties = build(b.properties, b.noValidate); 649 this.allowEmptyValue = resolve(b.allowEmptyValue); 650 this.exclusiveMaximum = resolve(b.exclusiveMaximum); 651 this.exclusiveMinimum = resolve(b.exclusiveMinimum); 652 this.required = resolve(b.required); 653 this.uniqueItems = resolve(b.uniqueItems); 654 this.skipIfEmpty = resolve(b.skipIfEmpty); 655 this.collectionFormat = b.collectionFormat; 656 this.type = b.type; 657 this.format = b.format; 658 this.pattern = b.pattern; 659 this.items = build(b.items, b.noValidate); 660 this.additionalProperties = build(b.additionalProperties, b.noValidate); 661 this.maximum = b.maximum; 662 this.minimum = b.minimum; 663 this.multipleOf = b.multipleOf; 664 this.maxItems = b.maxItems; 665 this.maxLength = b.maxLength; 666 this.maxProperties = b.maxProperties; 667 this.minItems = b.minItems; 668 this.minLength = b.minLength; 669 this.minProperties = b.minProperties; 670 this.parser = b.parser; 671 this.serializer = b.serializer; 672 673 // Calculate parse type 674 Class<?> parsedType = Object.class; 675 if (type == ARRAY) { 676 if (items != null) 677 parsedType = Array.newInstance(items.parsedType.getInnerClass(), 0).getClass(); 678 } else if (type == BOOLEAN) { 679 parsedType = Boolean.class; 680 } else if (type == INTEGER) { 681 if (format == INT64) 682 parsedType = Long.class; 683 else 684 parsedType = Integer.class; 685 } else if (type == NUMBER) { 686 if (format == DOUBLE) 687 parsedType = Double.class; 688 else 689 parsedType = Float.class; 690 } else if (type == STRING) { 691 if (format == BYTE || format == BINARY || format == BINARY_SPACED) 692 parsedType = byte[].class; 693 else if (format == DATE || format == DATE_TIME) 694 parsedType = Calendar.class; 695 else 696 parsedType = String.class; 697 } 698 this.parsedType = BeanContext.DEFAULT.getClassMeta(parsedType); 699 700 if (b.noValidate) 701 return; 702 703 // Validation. 704 List<String> errors = new ArrayList<>(); 705 AList<String> notAllowed = AList.of(); 706 boolean invalidFormat = false; 707 switch (type) { 708 case STRING: { 709 notAllowed.aif(properties != null, "properties"); 710 notAllowed.aif(additionalProperties != null, "additionalProperties"); 711 notAllowed.aif(exclusiveMaximum, "exclusiveMaximum"); 712 notAllowed.aif(exclusiveMinimum, "exclusiveMinimum"); 713 notAllowed.aif(uniqueItems, "uniqueItems"); 714 notAllowed.aif(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat"); 715 notAllowed.aif(items != null, "items"); 716 notAllowed.aif(maximum != null, "maximum"); 717 notAllowed.aif(minimum != null, "minimum"); 718 notAllowed.aif(multipleOf != null, "multipleOf"); 719 notAllowed.aif(maxItems != null, "maxItems"); 720 notAllowed.aif(minItems != null, "minItems"); 721 notAllowed.aif(minProperties != null, "minProperties"); 722 invalidFormat = ! format.isOneOf(HttpPartFormat.BYTE, HttpPartFormat.BINARY, HttpPartFormat.BINARY_SPACED, HttpPartFormat.DATE, HttpPartFormat.DATE_TIME, HttpPartFormat.PASSWORD, HttpPartFormat.UON, HttpPartFormat.NO_FORMAT); 723 break; 724 } 725 case ARRAY: { 726 notAllowed.aif(properties != null, "properties"); 727 notAllowed.aif(additionalProperties != null, "additionalProperties"); 728 notAllowed.aif(exclusiveMaximum, "exclusiveMaximum"); 729 notAllowed.aif(exclusiveMinimum, "exclusiveMinimum"); 730 notAllowed.aif(pattern != null, "pattern"); 731 notAllowed.aif(maximum != null, "maximum"); 732 notAllowed.aif(minimum != null, "minimum"); 733 notAllowed.aif(multipleOf != null, "multipleOf"); 734 notAllowed.aif(maxLength != null, "maxLength"); 735 notAllowed.aif(minLength != null, "minLength"); 736 notAllowed.aif(maxProperties != null, "maxProperties"); 737 notAllowed.aif(minProperties != null, "minProperties"); 738 invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON); 739 break; 740 } 741 case BOOLEAN: { 742 notAllowed.aif(! _enum.isEmpty(), "_enum"); 743 notAllowed.aif(properties != null, "properties"); 744 notAllowed.aif(additionalProperties != null, "additionalProperties"); 745 notAllowed.aif(exclusiveMaximum, "exclusiveMaximum"); 746 notAllowed.aif(exclusiveMinimum, "exclusiveMinimum"); 747 notAllowed.aif(uniqueItems, "uniqueItems"); 748 notAllowed.aif(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat"); 749 notAllowed.aif(pattern != null, "pattern"); 750 notAllowed.aif(items != null, "items"); 751 notAllowed.aif(maximum != null, "maximum"); 752 notAllowed.aif(minimum != null, "minimum"); 753 notAllowed.aif(multipleOf != null, "multipleOf"); 754 notAllowed.aif(maxItems != null, "maxItems"); 755 notAllowed.aif(maxLength != null, "maxLength"); 756 notAllowed.aif(maxProperties != null, "maxProperties"); 757 notAllowed.aif(minItems != null, "minItems"); 758 notAllowed.aif(minLength != null, "minLength"); 759 notAllowed.aif(minProperties != null, "minProperties"); 760 invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON); 761 break; 762 } 763 case FILE: { 764 break; 765 } 766 case INTEGER: { 767 notAllowed.aif(properties != null, "properties"); 768 notAllowed.aif(additionalProperties != null, "additionalProperties"); 769 notAllowed.aif(uniqueItems, "uniqueItems"); 770 notAllowed.aif(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat"); 771 notAllowed.aif(pattern != null, "pattern"); 772 notAllowed.aif(items != null, "items"); 773 notAllowed.aif(maxItems != null, "maxItems"); 774 notAllowed.aif(maxLength != null, "maxLength"); 775 notAllowed.aif(maxProperties != null, "maxProperties"); 776 notAllowed.aif(minItems != null, "minItems"); 777 notAllowed.aif(minLength != null, "minLength"); 778 notAllowed.aif(minProperties != null, "minProperties"); 779 invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON, HttpPartFormat.INT32, HttpPartFormat.INT64); 780 break; 781 } 782 case NUMBER: { 783 notAllowed.aif(properties != null, "properties"); 784 notAllowed.aif(additionalProperties != null, "additionalProperties"); 785 notAllowed.aif(uniqueItems, "uniqueItems"); 786 notAllowed.aif(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat"); 787 notAllowed.aif(pattern != null, "pattern"); 788 notAllowed.aif(items != null, "items"); 789 notAllowed.aif(maxItems != null, "maxItems"); 790 notAllowed.aif(maxLength != null, "maxLength"); 791 notAllowed.aif(maxProperties != null, "maxProperties"); 792 notAllowed.aif(minItems != null, "minItems"); 793 notAllowed.aif(minLength != null, "minLength"); 794 notAllowed.aif(minProperties != null, "minProperties"); 795 invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT, HttpPartFormat.UON, HttpPartFormat.FLOAT, HttpPartFormat.DOUBLE); 796 break; 797 } 798 case OBJECT: { 799 notAllowed.aif(exclusiveMaximum, "exclusiveMaximum"); 800 notAllowed.aif(exclusiveMinimum, "exclusiveMinimum"); 801 notAllowed.aif(uniqueItems, "uniqueItems"); 802 notAllowed.aif(pattern != null, "pattern"); 803 notAllowed.aif(items != null, "items"); 804 notAllowed.aif(maximum != null, "maximum"); 805 notAllowed.aif(minimum != null, "minimum"); 806 notAllowed.aif(multipleOf != null, "multipleOf"); 807 notAllowed.aif(maxItems != null, "maxItems"); 808 notAllowed.aif(maxLength != null, "maxLength"); 809 notAllowed.aif(minItems != null, "minItems"); 810 notAllowed.aif(minLength != null, "minLength"); 811 invalidFormat = ! format.isOneOf(HttpPartFormat.NO_FORMAT); 812 break; 813 } 814 default: 815 break; 816 } 817 818 if (! notAllowed.isEmpty()) 819 errors.add("Attributes not allow for type='"+type+"': " + StringUtils.join(notAllowed, ",")); 820 if (invalidFormat) 821 errors.add("Invalid format for type='"+type+"': '"+format+"'"); 822 if (exclusiveMaximum && maximum == null) 823 errors.add("Cannot specify exclusiveMaximum with maximum."); 824 if (exclusiveMinimum && minimum == null) 825 errors.add("Cannot specify exclusiveMinimum with minimum."); 826 if (required && _default != null) 827 errors.add("Cannot specify a default value on a required value."); 828 if (minLength != null && maxLength != null && maxLength < minLength) 829 errors.add("maxLength cannot be less than minLength."); 830 if (minimum != null && maximum != null && maximum.doubleValue() < minimum.doubleValue()) 831 errors.add("maximum cannot be less than minimum."); 832 if (minItems != null && maxItems != null && maxItems < minItems) 833 errors.add("maxItems cannot be less than minItems."); 834 if (minProperties != null && maxProperties != null && maxProperties < minProperties) 835 errors.add("maxProperties cannot be less than minProperties."); 836 if (minLength != null && minLength < 0) 837 errors.add("minLength cannot be less than zero."); 838 if (maxLength != null && maxLength < 0) 839 errors.add("maxLength cannot be less than zero."); 840 if (minItems != null && minItems < 0) 841 errors.add("minItems cannot be less than zero."); 842 if (maxItems != null && maxItems < 0) 843 errors.add("maxItems cannot be less than zero."); 844 if (minProperties != null && minProperties < 0) 845 errors.add("minProperties cannot be less than zero."); 846 if (maxProperties != null && maxProperties < 0) 847 errors.add("maxProperties cannot be less than zero."); 848 if (type == ARRAY && items != null && items.getType() == OBJECT && (format != UON && format != HttpPartFormat.NO_FORMAT)) 849 errors.add("Cannot define an array of objects unless array format is 'uon'."); 850 851 if (! errors.isEmpty()) 852 throw new ContextRuntimeException("Schema specification errors: \n\t" + join(errors, "\n\t"), new Object[0]); 853 } 854 855 /** 856 * Returns the default parsed type for this schema. 857 * 858 * @return The default parsed type for this schema. Never <jk>null</jk>. 859 */ 860 public ClassMeta<?> getParsedType() { 861 return parsedType; 862 } 863 864 /** 865 * Returns the name of the object described by this schema, for example the query or form parameter name. 866 * 867 * @return The name, or <jk>null</jk> if not specified. 868 * @see HttpPartSchemaBuilder#name(String) 869 */ 870 public String getName() { 871 return name; 872 } 873 874 /** 875 * Returns the HTTP status code or codes defined on a schema. 876 * 877 * @return 878 * The list of HTTP status codes. 879 * <br>Never <jk>null</jk>. 880 * @see HttpPartSchemaBuilder#code(int) 881 * @see HttpPartSchemaBuilder#codes(int[]) 882 */ 883 public Set<Integer> getCodes() { 884 return codes; 885 } 886 887 /** 888 * Returns the HTTP status code or codes defined on a schema. 889 * 890 * @param def The default value if there are no codes defined. 891 * @return 892 * The list of HTTP status codes. 893 * <br>A singleton set containing the default value if the set is empty. 894 * <br>Never <jk>null</jk>. 895 * @see HttpPartSchemaBuilder#code(int) 896 * @see HttpPartSchemaBuilder#codes(int[]) 897 */ 898 public Set<Integer> getCodes(Integer def) { 899 return codes.isEmpty() ? Collections.singleton(def) : codes; 900 } 901 902 /** 903 * Returns the first HTTP status code on a schema. 904 * 905 * @param def The default value if there are no codes defined. 906 * @return 907 * The list of HTTP status codes. 908 * <br>A singleton set containing the default value if the set is empty. 909 * <br>Never <jk>null</jk>. 910 * @see HttpPartSchemaBuilder#code(int) 911 * @see HttpPartSchemaBuilder#codes(int[]) 912 */ 913 public Integer getCode(Integer def) { 914 return codes.isEmpty() ? def : codes.iterator().next(); 915 } 916 917 /** 918 * Returns the <c>type</c> field of this schema. 919 * 920 * @return The <c>type</c> field of this schema, or <jk>null</jk> if not specified. 921 * @see HttpPartSchemaBuilder#type(String) 922 */ 923 public HttpPartDataType getType() { 924 return type; 925 } 926 927 /** 928 * Returns the <c>type</c> field of this schema. 929 * 930 * @param cm 931 * The class meta of the object. 932 * <br>Used to auto-detect the type if the type was not specified. 933 * @return The format field of this schema, or <jk>null</jk> if not specified. 934 * @see HttpPartSchemaBuilder#format(String) 935 */ 936 public HttpPartDataType getType(ClassMeta<?> cm) { 937 if (type != HttpPartDataType.NO_TYPE) 938 return type; 939 if (cm.isTemporal() || cm.isDateOrCalendar()) 940 return HttpPartDataType.STRING; 941 if (cm.isNumber()) { 942 if (cm.isDecimal()) 943 return HttpPartDataType.NUMBER; 944 return HttpPartDataType.INTEGER; 945 } 946 if (cm.isBoolean()) 947 return HttpPartDataType.BOOLEAN; 948 if (cm.isMapOrBean()) 949 return HttpPartDataType.OBJECT; 950 if (cm.isCollectionOrArray()) 951 return HttpPartDataType.ARRAY; 952 return HttpPartDataType.STRING; 953 } 954 955 /** 956 * Returns the <c>default</c> field of this schema. 957 * 958 * @return The default value for this schema, or <jk>null</jk> if not specified. 959 * @see HttpPartSchemaBuilder#_default(String) 960 */ 961 public String getDefault() { 962 return _default; 963 } 964 965 /** 966 * Returns the <c>collectionFormat</c> field of this schema. 967 * 968 * @return The <c>collectionFormat</c> field of this schema, or <jk>null</jk> if not specified. 969 * @see HttpPartSchemaBuilder#collectionFormat(String) 970 */ 971 public HttpPartCollectionFormat getCollectionFormat() { 972 return collectionFormat; 973 } 974 975 /** 976 * Returns the <c>format</c> field of this schema. 977 * 978 * @see HttpPartSchemaBuilder#format(String) 979 * @return The <c>format</c> field of this schema, or <jk>null</jk> if not specified. 980 */ 981 public HttpPartFormat getFormat() { 982 return format; 983 } 984 985 /** 986 * Returns the <c>format</c> field of this schema. 987 * 988 * @param cm 989 * The class meta of the object. 990 * <br>Used to auto-detect the format if the format was not specified. 991 * @return The <c>format</c> field of this schema, or <jk>null</jk> if not specified. 992 * @see HttpPartSchemaBuilder#format(String) 993 */ 994 public HttpPartFormat getFormat(ClassMeta<?> cm) { 995 if (format != HttpPartFormat.NO_FORMAT) 996 return format; 997 if (cm.isNumber()) { 998 if (cm.isDecimal()) { 999 if (cm.isDouble()) 1000 return HttpPartFormat.DOUBLE; 1001 return HttpPartFormat.FLOAT; 1002 } 1003 if (cm.isLong()) 1004 return HttpPartFormat.INT64; 1005 return HttpPartFormat.INT32; 1006 } 1007 return format; 1008 } 1009 1010 /** 1011 * Returns the <c>maximum</c> field of this schema. 1012 * 1013 * @return The schema for child items of the object represented by this schema, or <jk>null</jk> if not defined. 1014 * @see HttpPartSchemaBuilder#items(HttpPartSchemaBuilder) 1015 */ 1016 public HttpPartSchema getItems() { 1017 return items; 1018 } 1019 1020 /** 1021 * Returns the <c>maximum</c> field of this schema. 1022 * 1023 * @return The <c>maximum</c> field of this schema, or <jk>null</jk> if not specified. 1024 * @see HttpPartSchemaBuilder#maximum(Number) 1025 */ 1026 public Number getMaximum() { 1027 return maximum; 1028 } 1029 1030 /** 1031 * Returns the <c>minimum</c> field of this schema. 1032 * 1033 * @return The <c>minimum</c> field of this schema, or <jk>null</jk> if not specified. 1034 * @see HttpPartSchemaBuilder#minimum(Number) 1035 */ 1036 public Number getMinimum() { 1037 return minimum; 1038 } 1039 1040 /** 1041 * Returns the <c>xxx</c> field of this schema. 1042 * 1043 * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified. 1044 * @see HttpPartSchemaBuilder#multipleOf(Number) 1045 */ 1046 public Number getMultipleOf() { 1047 return multipleOf; 1048 } 1049 1050 /** 1051 * Returns the <c>xxx</c> field of this schema. 1052 * 1053 * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified. 1054 * @see HttpPartSchemaBuilder#pattern(String) 1055 */ 1056 public Pattern getPattern() { 1057 return pattern; 1058 } 1059 1060 /** 1061 * Returns the <c>xxx</c> field of this schema. 1062 * 1063 * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified. 1064 * @see HttpPartSchemaBuilder#maxLength(Long) 1065 */ 1066 public Long getMaxLength() { 1067 return maxLength; 1068 } 1069 1070 /** 1071 * Returns the <c>xxx</c> field of this schema. 1072 * 1073 * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified. 1074 * @see HttpPartSchemaBuilder#minLength(Long) 1075 */ 1076 public Long getMinLength() { 1077 return minLength; 1078 } 1079 1080 /** 1081 * Returns the <c>xxx</c> field of this schema. 1082 * 1083 * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified. 1084 * @see HttpPartSchemaBuilder#maxItems(Long) 1085 */ 1086 public Long getMaxItems() { 1087 return maxItems; 1088 } 1089 1090 /** 1091 * Returns the <c>xxx</c> field of this schema. 1092 * 1093 * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified. 1094 * @see HttpPartSchemaBuilder#minItems(Long) 1095 */ 1096 public Long getMinItems() { 1097 return minItems; 1098 } 1099 1100 /** 1101 * Returns the <c>xxx</c> field of this schema. 1102 * 1103 * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified. 1104 * @see HttpPartSchemaBuilder#maxProperties(Long) 1105 */ 1106 public Long getMaxProperties() { 1107 return maxProperties; 1108 } 1109 1110 /** 1111 * Returns the <c>xxx</c> field of this schema. 1112 * 1113 * @return The <c>xxx</c> field of this schema, or <jk>null</jk> if not specified. 1114 * @see HttpPartSchemaBuilder#minProperties(Long) 1115 */ 1116 public Long getMinProperties() { 1117 return minProperties; 1118 } 1119 1120 /** 1121 * Returns the <c>exclusiveMaximum</c> field of this schema. 1122 * 1123 * @return The <c>exclusiveMaximum</c> field of this schema. 1124 * @see HttpPartSchemaBuilder#exclusiveMaximum(Boolean) 1125 */ 1126 public boolean isExclusiveMaximum() { 1127 return exclusiveMaximum; 1128 } 1129 1130 /** 1131 * Returns the <c>exclusiveMinimum</c> field of this schema. 1132 * 1133 * @return The <c>exclusiveMinimum</c> field of this schema. 1134 * @see HttpPartSchemaBuilder#exclusiveMinimum(Boolean) 1135 */ 1136 public boolean isExclusiveMinimum() { 1137 return exclusiveMinimum; 1138 } 1139 1140 /** 1141 * Returns the <c>uniqueItems</c> field of this schema. 1142 * 1143 * @return The <c>uniqueItems</c> field of this schema. 1144 * @see HttpPartSchemaBuilder#uniqueItems(Boolean) 1145 */ 1146 public boolean isUniqueItems() { 1147 return uniqueItems; 1148 } 1149 1150 /** 1151 * Returns the <c>required</c> field of this schema. 1152 * 1153 * @return The <c>required</c> field of this schema. 1154 * @see HttpPartSchemaBuilder#required(Boolean) 1155 */ 1156 public boolean isRequired() { 1157 return required; 1158 } 1159 1160 /** 1161 * Returns the <c>skipIfEmpty</c> field of this schema. 1162 * 1163 * @return The <c>skipIfEmpty</c> field of this schema. 1164 * @see HttpPartSchemaBuilder#skipIfEmpty(Boolean) 1165 */ 1166 public boolean isSkipIfEmpty() { 1167 return skipIfEmpty; 1168 } 1169 1170 /** 1171 * Returns the <c>allowEmptyValue</c> field of this schema. 1172 * 1173 * @return The <c>skipIfEmpty</c> field of this schema. 1174 * @see HttpPartSchemaBuilder#skipIfEmpty(Boolean) 1175 */ 1176 public boolean isAllowEmptyValue() { 1177 return allowEmptyValue; 1178 } 1179 1180 /** 1181 * Returns the <c>enum</c> field of this schema. 1182 * 1183 * @return The <c>enum</c> field of this schema, or <jk>null</jk> if not specified. 1184 * @see HttpPartSchemaBuilder#_enum(Set) 1185 */ 1186 public Set<String> getEnum() { 1187 return _enum; 1188 } 1189 1190 /** 1191 * Returns the <c>parser</c> field of this schema. 1192 * 1193 * @return The <c>parser</c> field of this schema, or <jk>null</jk> if not specified. 1194 * @see HttpPartSchemaBuilder#parser(Class) 1195 */ 1196 public Class<? extends HttpPartParser> getParser() { 1197 return parser; 1198 } 1199 1200 /** 1201 * Returns the <c>serializer</c> field of this schema. 1202 * 1203 * @return The <c>serializer</c> field of this schema, or <jk>null</jk> if not specified. 1204 * @see HttpPartSchemaBuilder#serializer(Class) 1205 */ 1206 public Class<? extends HttpPartSerializer> getSerializer() { 1207 return serializer; 1208 } 1209 1210 /** 1211 * Throws a {@link ParseException} if the specified pre-parsed input does not validate against this schema. 1212 * 1213 * @param in The input. 1214 * @return The same object passed in. 1215 * @throws SchemaValidationException if the specified pre-parsed input does not validate against this schema. 1216 */ 1217 public String validateInput(String in) throws SchemaValidationException { 1218 if (! isValidRequired(in)) 1219 throw new SchemaValidationException("No value specified."); 1220 if (in != null) { 1221 if (! isValidAllowEmpty(in)) 1222 throw new SchemaValidationException("Empty value not allowed."); 1223 if (! isValidPattern(in)) 1224 throw new SchemaValidationException("Value does not match expected pattern. Must match pattern: {0}", pattern.pattern()); 1225 if (! isValidEnum(in)) 1226 throw new SchemaValidationException("Value does not match one of the expected values. Must be one of the following: {0}", _enum); 1227 if (! isValidMaxLength(in)) 1228 throw new SchemaValidationException("Maximum length of value exceeded."); 1229 if (! isValidMinLength(in)) 1230 throw new SchemaValidationException("Minimum length of value not met."); 1231 } 1232 return in; 1233 } 1234 1235 /** 1236 * Throws a {@link ParseException} if the specified parsed output does not validate against this schema. 1237 * 1238 * @param o The parsed output. 1239 * @param bc The bean context used to detect POJO types. 1240 * @return The same object passed in. 1241 * @throws SchemaValidationException if the specified parsed output does not validate against this schema. 1242 */ 1243 @SuppressWarnings("rawtypes") 1244 public <T> T validateOutput(T o, BeanContext bc) throws SchemaValidationException { 1245 if (o == null) { 1246 if (! isValidRequired(o)) 1247 throw new SchemaValidationException("Required value not provided."); 1248 return o; 1249 } 1250 ClassMeta<?> cm = bc.getClassMetaForObject(o); 1251 switch (getType(cm)) { 1252 case ARRAY: { 1253 if (cm.isArray()) { 1254 if (! isValidMinItems(o)) 1255 throw new SchemaValidationException("Minimum number of items not met."); 1256 if (! isValidMaxItems(o)) 1257 throw new SchemaValidationException("Maximum number of items exceeded."); 1258 if (! isValidUniqueItems(o)) 1259 throw new SchemaValidationException("Duplicate items not allowed."); 1260 HttpPartSchema items = getItems(); 1261 if (items != null) 1262 for (int i = 0; i < Array.getLength(o); i++) 1263 items.validateOutput(Array.get(o, i), bc); 1264 } else if (cm.isCollection()) { 1265 Collection<?> c = (Collection<?>)o; 1266 if (! isValidMinItems(c)) 1267 throw new SchemaValidationException("Minimum number of items not met."); 1268 if (! isValidMaxItems(c)) 1269 throw new SchemaValidationException("Maximum number of items exceeded."); 1270 if (! isValidUniqueItems(c)) 1271 throw new SchemaValidationException("Duplicate items not allowed."); 1272 HttpPartSchema items = getItems(); 1273 if (items != null) 1274 for (Object o2 : c) 1275 items.validateOutput(o2, bc); 1276 } 1277 break; 1278 } 1279 case INTEGER: { 1280 if (cm.isNumber()) { 1281 Number n = (Number)o; 1282 if (! isValidMinimum(n)) 1283 throw new SchemaValidationException("Minimum value not met."); 1284 if (! isValidMaximum(n)) 1285 throw new SchemaValidationException("Maximum value exceeded."); 1286 if (! isValidMultipleOf(n)) 1287 throw new SchemaValidationException("Multiple-of not met."); 1288 } 1289 break; 1290 } 1291 case NUMBER: { 1292 if (cm.isNumber()) { 1293 Number n = (Number)o; 1294 if (! isValidMinimum(n)) 1295 throw new SchemaValidationException("Minimum value not met."); 1296 if (! isValidMaximum(n)) 1297 throw new SchemaValidationException("Maximum value exceeded."); 1298 if (! isValidMultipleOf(n)) 1299 throw new SchemaValidationException("Multiple-of not met."); 1300 } 1301 break; 1302 } 1303 case OBJECT: { 1304 if (cm.isMapOrBean()) { 1305 Map<?,?> m = cm.isMap() ? (Map<?,?>)o : bc.createSession().toBeanMap(o); 1306 if (! isValidMinProperties(m)) 1307 throw new SchemaValidationException("Minimum number of properties not met."); 1308 if (! isValidMaxProperties(m)) 1309 throw new SchemaValidationException("Maximum number of properties exceeded."); 1310 for (Map.Entry e : m.entrySet()) { 1311 String key = e.getKey().toString(); 1312 HttpPartSchema s2 = getProperty(key); 1313 if (s2 != null) 1314 s2.validateOutput(e.getValue(), bc); 1315 } 1316 } else if (cm.isBean()) { 1317 1318 } 1319 break; 1320 } 1321 case BOOLEAN: 1322 case FILE: 1323 case STRING: 1324 case NO_TYPE: 1325 break; 1326 } 1327 return o; 1328 } 1329 1330 //----------------------------------------------------------------------------------------------------------------- 1331 // Helper methods. 1332 //----------------------------------------------------------------------------------------------------------------- 1333 1334 private boolean isValidRequired(Object x) { 1335 return x != null || ! required; 1336 } 1337 1338 private boolean isValidMinProperties(Map<?,?> x) { 1339 return minProperties == null || x.size() >= minProperties; 1340 } 1341 1342 private boolean isValidMaxProperties(Map<?,?> x) { 1343 return maxProperties == null || x.size() <= maxProperties; 1344 } 1345 1346 private boolean isValidMinimum(Number x) { 1347 if (x instanceof Integer || x instanceof AtomicInteger) 1348 return minimum == null || x.intValue() > minimum.intValue() || (x.intValue() == minimum.intValue() && (! exclusiveMinimum)); 1349 if (x instanceof Short || x instanceof Byte) 1350 return minimum == null || x.shortValue() > minimum.shortValue() || (x.intValue() == minimum.shortValue() && (! exclusiveMinimum)); 1351 if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger) 1352 return minimum == null || x.longValue() > minimum.longValue() || (x.intValue() == minimum.longValue() && (! exclusiveMinimum)); 1353 if (x instanceof Float) 1354 return minimum == null || x.floatValue() > minimum.floatValue() || (x.floatValue() == minimum.floatValue() && (! exclusiveMinimum)); 1355 if (x instanceof Double || x instanceof BigDecimal) 1356 return minimum == null || x.doubleValue() > minimum.doubleValue() || (x.doubleValue() == minimum.doubleValue() && (! exclusiveMinimum)); 1357 return true; 1358 } 1359 1360 private boolean isValidMaximum(Number x) { 1361 if (x instanceof Integer || x instanceof AtomicInteger) 1362 return maximum == null || x.intValue() < maximum.intValue() || (x.intValue() == maximum.intValue() && (! exclusiveMaximum)); 1363 if (x instanceof Short || x instanceof Byte) 1364 return maximum == null || x.shortValue() < maximum.shortValue() || (x.intValue() == maximum.shortValue() && (! exclusiveMaximum)); 1365 if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger) 1366 return maximum == null || x.longValue() < maximum.longValue() || (x.intValue() == maximum.longValue() && (! exclusiveMaximum)); 1367 if (x instanceof Float) 1368 return maximum == null || x.floatValue() < maximum.floatValue() || (x.floatValue() == maximum.floatValue() && (! exclusiveMaximum)); 1369 if (x instanceof Double || x instanceof BigDecimal) 1370 return maximum == null || x.doubleValue() < maximum.doubleValue() || (x.doubleValue() == maximum.doubleValue() && (! exclusiveMaximum)); 1371 return true; 1372 } 1373 1374 private boolean isValidMultipleOf(Number x) { 1375 if (x instanceof Integer || x instanceof AtomicInteger) 1376 return multipleOf == null || x.intValue() % multipleOf.intValue() == 0; 1377 if (x instanceof Short || x instanceof Byte) 1378 return multipleOf == null || x.shortValue() % multipleOf.shortValue() == 0; 1379 if (x instanceof Long || x instanceof AtomicLong || x instanceof BigInteger) 1380 return multipleOf == null || x.longValue() % multipleOf.longValue() == 0; 1381 if (x instanceof Float) 1382 return multipleOf == null || x.floatValue() % multipleOf.floatValue() == 0; 1383 if (x instanceof Double || x instanceof BigDecimal) 1384 return multipleOf == null || x.doubleValue() % multipleOf.doubleValue() == 0; 1385 return true; 1386 } 1387 1388 private boolean isValidAllowEmpty(String x) { 1389 return allowEmptyValue || isNotEmpty(x); 1390 } 1391 1392 private boolean isValidPattern(String x) { 1393 return pattern == null || pattern.matcher(x).matches(); 1394 } 1395 1396 private boolean isValidEnum(String x) { 1397 return _enum.isEmpty() || _enum.contains(x); 1398 } 1399 1400 private boolean isValidMinLength(String x) { 1401 return minLength == null || x.length() >= minLength; 1402 } 1403 1404 private boolean isValidMaxLength(String x) { 1405 return maxLength == null || x.length() <= maxLength; 1406 } 1407 1408 private boolean isValidMinItems(Object x) { 1409 return minItems == null || Array.getLength(x) >= minItems; 1410 } 1411 1412 private boolean isValidMaxItems(Object x) { 1413 return maxItems == null || Array.getLength(x) <= maxItems; 1414 } 1415 1416 private boolean isValidUniqueItems(Object x) { 1417 if (uniqueItems) { 1418 Set<Object> s = new HashSet<>(); 1419 for (int i = 0; i < Array.getLength(x); i++) { 1420 Object o = Array.get(x, i); 1421 if (! s.add(o)) 1422 return false; 1423 } 1424 } 1425 return true; 1426 } 1427 1428 private boolean isValidMinItems(Collection<?> x) { 1429 return minItems == null || x.size() >= minItems; 1430 } 1431 1432 private boolean isValidMaxItems(Collection<?> x) { 1433 return maxItems == null || x.size() <= maxItems; 1434 } 1435 1436 private boolean isValidUniqueItems(Collection<?> x) { 1437 if (uniqueItems && ! (x instanceof Set)) { 1438 Set<Object> s = new HashSet<>(); 1439 for (Object o : x) 1440 if (! s.add(o)) 1441 return false; 1442 } 1443 return true; 1444 } 1445 1446 /** 1447 * Returns the schema information for the specified property. 1448 * 1449 * @param name The property name. 1450 * @return The schema information for the specified property, or <jk>null</jk> if properties are not defined on this schema. 1451 */ 1452 public HttpPartSchema getProperty(String name) { 1453 if (properties != null) { 1454 HttpPartSchema schema = properties.get(name); 1455 if (schema != null) 1456 return schema; 1457 } 1458 return additionalProperties; 1459 } 1460 1461 /** 1462 * Returns <jk>true</jk> if this schema has properties associated with it. 1463 * 1464 * @return <jk>true</jk> if this schema has properties associated with it. 1465 */ 1466 public boolean hasProperties() { 1467 return properties != null || additionalProperties != null; 1468 } 1469 1470 private static <T> Set<T> copy(Set<T> in) { 1471 return in == null ? Collections.emptySet() : unmodifiableSet(new LinkedHashSet<>(in)); 1472 } 1473 1474 private static Map<String,HttpPartSchema> build(Map<String,Object> in, boolean noValidate) { 1475 if (in == null) 1476 return null; 1477 Map<String,HttpPartSchema> m = new LinkedHashMap<>(); 1478 for (Map.Entry<String,Object> e : in.entrySet()) { 1479 Object v = e.getValue(); 1480 m.put(e.getKey(), build(v, noValidate)); 1481 } 1482 return unmodifiableMap(m); 1483 } 1484 1485 private static HttpPartSchema build(Object in, boolean noValidate) { 1486 if (in == null) 1487 return null; 1488 if (in instanceof HttpPartSchema) 1489 return (HttpPartSchema)in; 1490 return ((HttpPartSchemaBuilder)in).noValidate(noValidate).build(); 1491 } 1492 1493 //----------------------------------------------------------------------------------------------------------------- 1494 // Helper methods. 1495 //----------------------------------------------------------------------------------------------------------------- 1496 1497 private boolean resolve(Boolean b) { 1498 return b == null ? false : b; 1499 } 1500 1501 final static Set<String> toSet(String[]...s) { 1502 for (String[] ss : s) 1503 if (ss != null && ss.length > 0) 1504 return toSet(joinnl(ss)); 1505 return null; 1506 } 1507 1508 final static Set<String> toSet(String s) { 1509 if (isEmpty(s)) 1510 return null; 1511 Set<String> set = ASet.of(); 1512 try { 1513 for (Object o : StringUtils.parseListOrCdl(s)) 1514 set.add(o.toString()); 1515 } catch (ParseException e) { 1516 throw new RuntimeException(e); 1517 } 1518 return set; 1519 } 1520 1521 final static Number toNumber(String...s) { 1522 try { 1523 for (String ss : s) 1524 if (isNotEmpty(ss)) 1525 return parseNumber(ss, Number.class); 1526 return null; 1527 } catch (ParseException e) { 1528 throw new RuntimeException(e); 1529 } 1530 } 1531 1532 final static OMap toOMap(String[] ss) { 1533 String s = joinnl(ss); 1534 if (s.isEmpty()) 1535 return null; 1536 if (! isJsonObject(s, true)) 1537 s = "{" + s + "}"; 1538 try { 1539 return OMap.ofJson(s); 1540 } catch (ParseException e) { 1541 throw new RuntimeException(e); 1542 } 1543 } 1544 1545 @Override 1546 public String toString() { 1547 try { 1548 OMap m = new OMap() 1549 .ase("name", name) 1550 .ase("type", type) 1551 .ase("format", format) 1552 .ase("codes", codes) 1553 .ase("default", _default) 1554 .ase("enum", _enum) 1555 .ase("properties", properties) 1556 .asf("allowEmptyValue", allowEmptyValue) 1557 .asf("exclusiveMaximum", exclusiveMaximum) 1558 .asf("exclusiveMinimum", exclusiveMinimum) 1559 .asf("required", required) 1560 .asf("uniqueItems", uniqueItems) 1561 .asf("skipIfEmpty", skipIfEmpty) 1562 .aif(collectionFormat != HttpPartCollectionFormat.NO_COLLECTION_FORMAT, "collectionFormat", collectionFormat) 1563 .ase("pattern", pattern) 1564 .asn("items", items) 1565 .asn("additionalProperties", additionalProperties) 1566 .asmo("maximum", maximum) 1567 .asmo("minimum", minimum) 1568 .asmo("multipleOf", multipleOf) 1569 .asmo("maxLength", maxLength) 1570 .asmo("minLength", minLength) 1571 .asmo("maxItems", maxItems) 1572 .asmo("minItems", minItems) 1573 .asmo("maxProperties", maxProperties) 1574 .asmo("minProperties", minProperties) 1575 .append("parsedType", parsedType) 1576 ; 1577 return m.toString(); 1578 } catch (Exception e) { 1579 e.printStackTrace(); 1580 return ""; 1581 } 1582 } 1583}