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.dto.swagger; 014 015import static org.apache.juneau.common.internal.StringUtils.*; 016import static org.apache.juneau.internal.CollectionUtils.*; 017import static org.apache.juneau.internal.ConverterUtils.*; 018 019import java.util.*; 020 021import org.apache.juneau.*; 022import org.apache.juneau.annotation.*; 023import org.apache.juneau.collections.*; 024import org.apache.juneau.internal.*; 025import org.apache.juneau.marshaller.*; 026 027/** 028 * A limited subset of JSON-Schema's items object. 029 * 030 * <p> 031 * It is used by parameter definitions that are not located in "body". 032 * 033 * <h5 class='section'>Example:</h5> 034 * <p class='bjava'> 035 * <jc>// Construct using SwaggerBuilder.</jc> 036 * Items <jv>items</jv> = <jsm>items</jsm>(<js>"string"</js>).minLength(2); 037 * 038 * <jc>// Serialize using JsonSerializer.</jc> 039 * String <jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.toString(<jv>items</jv>); 040 * 041 * <jc>// Or just use toString() which does the same as above.</jc> 042 * <jv>json</jv> = <jv>items</jv>.toString(); 043 * </p> 044 * <p class='bjson'> 045 * <jc>// Output</jc> 046 * { 047 * <js>"type"</js>: <js>"string"</js>, 048 * <js>"minLength"</js>: 2 049 * } 050 * </p> 051 * 052 * <h5 class='section'>See Also:</h5><ul> 053 * <li class='link'><a class="doclink" href="../../../../../index.html#jrs.Swagger">Overview > juneau-rest-server > Swagger</a> 054 * </ul> 055 */ 056@Bean(properties="type,format,items,collectionFormat,default,maximum,exclusiveMaximum,minimum,exclusiveMinimum,maxLength,minLength,pattern,maxItems,minItems,uniqueItems,enum,multipleOf,$ref,*") 057@FluentSetters 058public class Items extends SwaggerElement { 059 060 private static final String[] VALID_TYPES = {"string", "number", "integer", "boolean", "array"}; 061 private static final String[] VALID_COLLECTION_FORMATS = {"csv","ssv","tsv","pipes","multi"}; 062 063 private String 064 type, 065 format, 066 collectionFormat, 067 pattern, 068 ref; 069 private Number 070 maximum, 071 minimum, 072 multipleOf; 073 private Integer 074 maxLength, 075 minLength, 076 maxItems, 077 minItems; 078 private Boolean 079 exclusiveMaximum, 080 exclusiveMinimum, 081 uniqueItems; 082 private Items items; 083 private Object _default; 084 private Set<Object> _enum; 085 086 /** 087 * Default constructor. 088 */ 089 public Items() {} 090 091 /** 092 * Copy constructor. 093 * 094 * @param copyFrom The object to copy. 095 */ 096 public Items(Items copyFrom) { 097 super(copyFrom); 098 099 this.collectionFormat = copyFrom.collectionFormat; 100 this._default = copyFrom._default; 101 this._enum = copyOf(copyFrom._enum); 102 this.exclusiveMaximum = copyFrom.exclusiveMaximum; 103 this.exclusiveMinimum = copyFrom.exclusiveMinimum; 104 this.format = copyFrom.format; 105 this.items = copyFrom.items == null ? null : copyFrom.items.copy(); 106 this.maximum = copyFrom.maximum; 107 this.maxItems = copyFrom.maxItems; 108 this.maxLength = copyFrom.maxLength; 109 this.minimum = copyFrom.minimum; 110 this.minItems = copyFrom.minItems; 111 this.minLength = copyFrom.minLength; 112 this.multipleOf = copyFrom.multipleOf; 113 this.pattern = copyFrom.pattern; 114 this.ref = copyFrom.ref; 115 this.type = copyFrom.type; 116 this.uniqueItems = copyFrom.uniqueItems; 117 } 118 119 /** 120 * Make a deep copy of this object. 121 * 122 * @return A deep copy of this object. 123 */ 124 public Items copy() { 125 return new Items(this); 126 } 127 128 129 @Override /* SwaggerElement */ 130 protected Items strict() { 131 super.strict(); 132 return this; 133 } 134 135 //----------------------------------------------------------------------------------------------------------------- 136 // Properties 137 //----------------------------------------------------------------------------------------------------------------- 138 139 /** 140 * Bean property getter: <property>collectionFormat</property>. 141 * 142 * <p> 143 * Determines the format of the array if type array is used. 144 * 145 * @return The property value, or <jk>null</jk> if it is not set. 146 */ 147 public String getCollectionFormat() { 148 return collectionFormat; 149 } 150 151 /** 152 * Bean property setter: <property>collectionFormat</property>. 153 * 154 * <p> 155 * Determines the format of the array if type array is used. 156 * 157 * @param value 158 * The new value for this property. 159 * <br>Valid values: 160 * <ul> 161 * <li><js>"csv"</js> (default) - comma separated values <c>foo,bar</c>. 162 * <li><js>"ssv"</js> - space separated values <c>foo bar</c>. 163 * <li><js>"tsv"</js> - tab separated values <c>foo\tbar</c>. 164 * <li><js>"pipes"</js> - pipe separated values <c>foo|bar</c>. 165 * </ul> 166 * <br>Can be <jk>null</jk> to unset the property. 167 * @return This object. 168 */ 169 public Items setCollectionFormat(String value) { 170 if (isStrict() && ! ArrayUtils.contains(value, VALID_COLLECTION_FORMATS)) 171 throw new BasicRuntimeException( 172 "Invalid value passed in to setCollectionFormat(String). Value=''{0}'', valid values={1}", 173 value, Json5.of(VALID_COLLECTION_FORMATS) 174 ); 175 collectionFormat = value; 176 return this; 177 } 178 179 /** 180 * Bean property getter: <property>default</property>. 181 * 182 * <p> 183 * Declares the value of the item that the server will use if none is provided. 184 * 185 * <h5 class='section'>Notes:</h5><ul> 186 * <li class='note'> 187 * <js>"default"</js> has no meaning for required items. 188 * <li class='note'> 189 * Unlike JSON Schema this value MUST conform to the defined <c>type</c> for the data type. 190 * </ul> 191 * 192 * @return The property value, or <jk>null</jk> if it is not set. 193 */ 194 public Object getDefault() { 195 return _default; 196 } 197 198 /** 199 * Bean property setter: <property>default</property>. 200 * 201 * <p> 202 * Declares the value of the item that the server will use if none is provided. 203 * 204 * <h5 class='section'>Notes:</h5><ul> 205 * <li class='note'> 206 * <js>"default"</js> has no meaning for required items. 207 * <li class='note'> 208 * Unlike JSON Schema this value MUST conform to the defined <c>type</c> for the data type. 209 * </ul> 210 * 211 * @param value 212 * The new value for this property. 213 * <br>Can be <jk>null</jk> to unset the property. 214 * @return This object. 215 */ 216 public Items setDefault(Object value) { 217 _default = value; 218 return this; 219 } 220 221 /** 222 * Bean property getter: <property>enum</property>. 223 * 224 * @return The property value, or <jk>null</jk> if it is not set. 225 */ 226 public Set<Object> getEnum() { 227 return _enum; 228 } 229 230 /** 231 * Bean property setter: <property>enum</property>. 232 * 233 * @param value 234 * The new value for this property. 235 * <br>Can be <jk>null</jk> to unset the property. 236 * @return This object. 237 */ 238 public Items setEnum(Collection<Object> value) { 239 _enum = setFrom(value); 240 return this; 241 } 242 243 /** 244 * Bean property setter: <property>enum</property>. 245 * 246 * @param value 247 * The new value for this property. 248 * @return This object. 249 */ 250 public Items setEnum(Object...value) { 251 return setEnum(Arrays.asList(value)); 252 } 253 254 /** 255 * Bean property fluent setter: <property>enum</property>. 256 * 257 * @param value 258 * The new value for this property. 259 * <br>String values can be JSON arrays. 260 * @return This object. 261 */ 262 public Items addEnum(Object...value) { 263 setEnum(setBuilder(_enum).sparse().add(value).build()); 264 return this; 265 } 266 267 /** 268 * Bean property getter: <property>exclusiveMaximum</property>. 269 * 270 * @return The property value, or <jk>null</jk> if it is not set. 271 */ 272 public Boolean getExclusiveMaximum() { 273 return exclusiveMaximum; 274 } 275 276 /** 277 * Bean property setter: <property>exclusiveMaximum</property>. 278 * 279 * @param value 280 * The new value for this property. 281 * <br>Can be <jk>null</jk> to unset the property. 282 * @return This object. 283 */ 284 public Items setExclusiveMaximum(Boolean value) { 285 exclusiveMaximum = value; 286 return this; 287 } 288 289 /** 290 * Bean property getter: <property>exclusiveMinimum</property>. 291 * 292 * @return The property value, or <jk>null</jk> if it is not set. 293 */ 294 public Boolean getExclusiveMinimum() { 295 return exclusiveMinimum; 296 } 297 298 /** 299 * Bean property setter: <property>exclusiveMinimum</property>. 300 * 301 * @param value 302 * The new value for this property. 303 * <br>Can be <jk>null</jk> to unset the property. 304 * @return This object. 305 */ 306 public Items setExclusiveMinimum(Boolean value) { 307 exclusiveMinimum = value; 308 return this; 309 } 310 311 /** 312 * Bean property getter: <property>format</property>. 313 * 314 * <p> 315 * The extending format for the previously mentioned <c>type</c>. 316 * 317 * @return The property value, or <jk>null</jk> if it is not set. 318 */ 319 public String getFormat() { 320 return format; 321 } 322 323 /** 324 * Bean property setter: <property>format</property>. 325 * 326 * <p> 327 * The extending format for the previously mentioned <c>type</c>. 328 * 329 * @param value 330 * The new value for this property. 331 * <br>Can be <jk>null</jk> to unset the property. 332 * @return This object. 333 */ 334 public Items setFormat(String value) { 335 format = value; 336 return this; 337 } 338 339 /** 340 * Bean property getter: <property>items</property>. 341 * 342 * <p> 343 * Describes the type of items in the array. 344 * 345 * @return The property value, or <jk>null</jk> if it is not set. 346 */ 347 public Items getItems() { 348 return items; 349 } 350 351 /** 352 * Bean property setter: <property>items</property>. 353 * 354 * <p> 355 * Describes the type of items in the array. 356 * 357 * @param value 358 * The new value for this property. 359 * <br>Property value is required if <c>type</c> is <js>"array"</js>. 360 * <br>Can be <jk>null</jk> to unset the property. 361 * @return This object. 362 */ 363 public Items setItems(Items value) { 364 items = value; 365 return this; 366 } 367 368 /** 369 * Bean property getter: <property>maximum</property>. 370 * 371 * @return The property value, or <jk>null</jk> if it is not set. 372 */ 373 public Number getMaximum() { 374 return maximum; 375 } 376 377 /** 378 * Bean property setter: <property>maximum</property>. 379 * 380 * @param value 381 * The new value for this property. 382 * <br>Can be <jk>null</jk> to unset the property. 383 * @return This object. 384 */ 385 public Items setMaximum(Number value) { 386 maximum = value; 387 return this; 388 } 389 390 /** 391 * Bean property getter: <property>maxItems</property>. 392 * 393 * @return The property value, or <jk>null</jk> if it is not set. 394 */ 395 public Integer getMaxItems() { 396 return maxItems; 397 } 398 399 /** 400 * Bean property setter: <property>maxItems</property>. 401 * 402 * @param value 403 * The new value for this property. 404 * <br>Can be <jk>null</jk> to unset the property. 405 * @return This object. 406 */ 407 public Items setMaxItems(Integer value) { 408 maxItems = value; 409 return this; 410 } 411 412 //----------------------------------------------------------------------------------------------------------------- 413 // maxLength 414 //----------------------------------------------------------------------------------------------------------------- 415 416 /** 417 * Bean property getter: <property>maxLength</property>. 418 * 419 * @return The property value, or <jk>null</jk> if it is not set. 420 */ 421 public Integer getMaxLength() { 422 return maxLength; 423 } 424 425 /** 426 * Bean property setter: <property>maxLength</property>. 427 * 428 * @param value 429 * The new value for this property. 430 * <br>Can be <jk>null</jk> to unset the property. 431 * @return This object. 432 */ 433 public Items setMaxLength(Integer value) { 434 maxLength = value; 435 return this; 436 } 437 438 /** 439 * Bean property getter: <property>minimum</property>. 440 * 441 * @return The property value, or <jk>null</jk> if it is not set. 442 */ 443 public Number getMinimum() { 444 return minimum; 445 } 446 447 /** 448 * Bean property setter: <property>minimum</property>. 449 * 450 * @param value 451 * The new value for this property. 452 * <br>Can be <jk>null</jk> to unset the property. 453 * @return This object. 454 */ 455 public Items setMinimum(Number value) { 456 minimum = value; 457 return this; 458 } 459 460 /** 461 * Bean property getter: <property>minItems</property>. 462 * 463 * @return The property value, or <jk>null</jk> if it is not set. 464 */ 465 public Integer getMinItems() { 466 return minItems; 467 } 468 469 /** 470 * Bean property setter: <property>minItems</property>. 471 * 472 * @param value 473 * The new value for this property. 474 * <br>Can be <jk>null</jk> to unset the property. 475 * @return This object. 476 */ 477 public Items setMinItems(Integer value) { 478 minItems = value; 479 return this; 480 } 481 482 /** 483 * Bean property getter: <property>minLength</property>. 484 * 485 * @return The property value, or <jk>null</jk> if it is not set. 486 */ 487 public Integer getMinLength() { 488 return minLength; 489 } 490 491 /** 492 * Bean property setter: <property>minLength</property>. 493 * 494 * @param value 495 * The new value for this property. 496 * <br>Can be <jk>null</jk> to unset the property. 497 * @return This object. 498 */ 499 public Items setMinLength(Integer value) { 500 minLength = value; 501 return this; 502 } 503 504 /** 505 * Bean property getter: <property>multipleOf</property>. 506 * 507 * @return The property value, or <jk>null</jk> if it is not set. 508 */ 509 public Number getMultipleOf() { 510 return multipleOf; 511 } 512 513 /** 514 * Bean property setter: <property>multipleOf</property>. 515 * 516 * @param value 517 * The new value for this property. 518 * <br>Can be <jk>null</jk> to unset the property. 519 * @return This object. 520 */ 521 public Items setMultipleOf(Number value) { 522 multipleOf = value; 523 return this; 524 } 525 526 /** 527 * Bean property getter: <property>pattern</property>. 528 * 529 * @return The property value, or <jk>null</jk> if it is not set. 530 */ 531 public String getPattern() { 532 return pattern; 533 } 534 535 /** 536 * Bean property setter: <property>pattern</property>. 537 * 538 * @param value 539 * The new value for this property. 540 * <br>This string SHOULD be a valid regular expression. 541 * <br>Can be <jk>null</jk> to unset the property. 542 * @return This object. 543 */ 544 public Items setPattern(String value) { 545 pattern = value; 546 return this; 547 } 548 549 /** 550 * Bean property getter: <property>$ref</property>. 551 * 552 * @return The property value, or <jk>null</jk> if it is not set. 553 */ 554 @Beanp("$ref") 555 public String getRef() { 556 return ref; 557 } 558 559 /** 560 * Bean property setter: <property>$ref</property>. 561 * 562 * @param value 563 * The new value for this property. 564 * <br>Can be <jk>null</jk> to unset the property. 565 * @return This object. 566 */ 567 @Beanp("$ref") 568 public Items setRef(String value) { 569 ref = value; 570 return this; 571 } 572 573 /** 574 * Bean property getter: <property>type</property>. 575 * 576 * <p> 577 * The internal type of the array. 578 * 579 * @return The property value, or <jk>null</jk> if it is not set. 580 */ 581 public String getType() { 582 return type; 583 } 584 585 /** 586 * Bean property setter: <property>type</property>. 587 * 588 * <p> 589 * The internal type of the array. 590 * 591 * @param value 592 * The new value for this property. 593 * <br>Valid values: 594 * <ul> 595 * <li><js>"string"</js> 596 * <li><js>"number"</js> 597 * <li><js>"integer"</js> 598 * <li><js>"boolean"</js> 599 * <li><js>"array"</js> 600 * </ul> 601 * <br>Property value is required. 602 * @return This object. 603 */ 604 public Items setType(String value) { 605 if (isStrict() && ! ArrayUtils.contains(value, VALID_TYPES)) 606 throw new BasicRuntimeException("Invalid value passed in to setType(String). Value=''{0}'', valid values={1}", value, Json5.of(VALID_TYPES)); 607 type = value; 608 return this; 609 } 610 611 /** 612 * Bean property getter: <property>uniqueItems</property>. 613 * 614 * @return The property value, or <jk>null</jk> if it is not set. 615 */ 616 public Boolean getUniqueItems() { 617 return uniqueItems; 618 } 619 620 /** 621 * Bean property setter: <property>uniqueItems</property>. 622 * 623 * @param value 624 * The new value for this property. 625 * <br>Can be <jk>null</jk> to unset the property. 626 * @return This object. 627 */ 628 public Items setUniqueItems(Boolean value) { 629 uniqueItems = value; 630 return this; 631 } 632 633 // <FluentSetters> 634 635 // </FluentSetters> 636 637 @Override /* SwaggerElement */ 638 public <T> T get(String property, Class<T> type) { 639 if (property == null) 640 return null; 641 switch (property) { 642 case "collectionFormat": return toType(getCollectionFormat(), type); 643 case "default": return toType(getDefault(), type); 644 case "enum": return toType(getEnum(), type); 645 case "exclusiveMaximum": return toType(getExclusiveMaximum(), type); 646 case "exclusiveMinimum": return toType(getExclusiveMinimum(), type); 647 case "format": return toType(getFormat(), type); 648 case "items": return toType(getItems(), type); 649 case "maximum": return toType(getMaximum(), type); 650 case "maxItems": return toType(getMaxItems(), type); 651 case "maxLength": return toType(getMaxLength(), type); 652 case "minimum": return toType(getMinimum(), type); 653 case "minItems": return toType(getMinItems(), type); 654 case "minLength": return toType(getMinLength(), type); 655 case "multipleOf": return toType(getMultipleOf(), type); 656 case "pattern": return toType(getPattern(), type); 657 case "$ref": return toType(getRef(), type); 658 case "type": return toType(getType(), type); 659 case "uniqueItems": return toType(getUniqueItems(), type); 660 default: return super.get(property, type); 661 } 662 } 663 664 @Override /* SwaggerElement */ 665 public Items set(String property, Object value) { 666 if (property == null) 667 return this; 668 switch (property) { 669 case "collectionFormat": return setCollectionFormat(stringify(value)); 670 case "default": return setDefault(value); 671 case "enum": return setEnum(listBuilder(Object.class).sparse().addAny(value).build()); 672 case "exclusiveMaximum": return setExclusiveMaximum(toBoolean(value)); 673 case "exclusiveMinimum": return setExclusiveMinimum(toBoolean(value)); 674 case "format": return setFormat(stringify(value)); 675 case "items": return setItems(toType(value,Items.class)); 676 case "maximum": return setMaximum(toNumber(value)); 677 case "maxItems": return setMaxItems(toInteger(value)); 678 case "maxLength": return setMaxLength(toInteger(value)); 679 case "minimum": return setMinimum(toNumber(value)); 680 case "minItems": return setMinItems(toInteger(value)); 681 case "minLength": return setMinLength(toInteger(value)); 682 case "multipleOf": return setMultipleOf(toNumber(value)); 683 case "pattern": return setPattern(stringify(value)); 684 case "$ref": return setRef(stringify(value)); 685 case "type": return setType(stringify(value)); 686 case "uniqueItems": return setUniqueItems(toBoolean(value)); 687 default: 688 super.set(property, value); 689 return this; 690 } 691 } 692 693 @Override /* SwaggerElement */ 694 public Set<String> keySet() { 695 Set<String> s = setBuilder(String.class) 696 .addIf(collectionFormat != null, "collectionFormat") 697 .addIf(_default != null, "default") 698 .addIf(_enum != null, "enum") 699 .addIf(exclusiveMaximum != null, "exclusiveMaximum") 700 .addIf(exclusiveMinimum != null, "exclusiveMinimum") 701 .addIf(format != null, "format") 702 .addIf(items != null, "items") 703 .addIf(maximum != null, "maximum") 704 .addIf(maxItems != null, "maxItems") 705 .addIf(maxLength != null, "maxLength") 706 .addIf(minimum != null, "minimum") 707 .addIf(minItems != null, "minItems") 708 .addIf(minLength != null, "minLength") 709 .addIf(multipleOf != null, "multipleOf") 710 .addIf(pattern != null, "pattern") 711 .addIf(ref != null, "$ref") 712 .addIf(type != null, "type") 713 .addIf(uniqueItems != null, "uniqueItems") 714 .build(); 715 return new MultiSet<>(s, super.keySet()); 716 } 717 718 /** 719 * Resolves any <js>"$ref"</js> attributes in this element. 720 * 721 * @param swagger The swagger document containing the definitions. 722 * @param refStack Keeps track of previously-visited references so that we don't cause recursive loops. 723 * @param maxDepth 724 * The maximum depth to resolve references. 725 * <br>After that level is reached, <c>$ref</c> references will be left alone. 726 * <br>Useful if you have very complex models and you don't want your swagger page to be overly-complex. 727 * @return 728 * This object with references resolved. 729 * <br>May or may not be the same object. 730 */ 731 public Items resolveRefs(Swagger swagger, Deque<String> refStack, int maxDepth) { 732 733 if (ref != null) { 734 if (refStack.contains(ref) || refStack.size() >= maxDepth) 735 return this; 736 refStack.addLast(ref); 737 Items r = swagger.findRef(ref, Items.class).resolveRefs(swagger, refStack, maxDepth); 738 refStack.removeLast(); 739 return r; 740 } 741 742 set("properties", resolveRefs(get("properties"), swagger, refStack, maxDepth)); 743 744 if (items != null) 745 items = items.resolveRefs(swagger, refStack, maxDepth); 746 747 set("example", null); 748 749 return this; 750 } 751 752 /* Resolve references in extra attributes */ 753 private Object resolveRefs(Object o, Swagger swagger, Deque<String> refStack, int maxDepth) { 754 if (o instanceof JsonMap) { 755 JsonMap om = (JsonMap)o; 756 Object ref = om.get("$ref"); 757 if (ref instanceof CharSequence) { 758 String sref = ref.toString(); 759 if (refStack.contains(sref) || refStack.size() >= maxDepth) 760 return o; 761 refStack.addLast(sref); 762 Object o2 = swagger.findRef(sref, Object.class); 763 o2 = resolveRefs(o2, swagger, refStack, maxDepth); 764 refStack.removeLast(); 765 return o2; 766 } 767 om.entrySet().forEach(x -> x.setValue(resolveRefs(x.getValue(), swagger, refStack, maxDepth))); 768 } 769 if (o instanceof JsonList) 770 for (ListIterator<Object> li = ((JsonList)o).listIterator(); li.hasNext();) 771 li.set(resolveRefs(li.next(), swagger, refStack, maxDepth)); 772 return o; 773 } 774}