001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. 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.apache.juneau.jsonschema; 018 019import static java.util.Collections.*; 020import static org.apache.juneau.collections.JsonMap.*; 021import static org.apache.juneau.common.utils.ThrowableUtils.*; 022import static org.apache.juneau.common.utils.Utils.*; 023import static org.apache.juneau.internal.CollectionUtils.addAll; 024 025import java.lang.annotation.*; 026import java.util.*; 027import java.util.concurrent.*; 028import java.util.regex.*; 029 030import org.apache.juneau.*; 031import org.apache.juneau.annotation.*; 032import org.apache.juneau.collections.*; 033import org.apache.juneau.common.utils.*; 034import org.apache.juneau.internal.*; 035import org.apache.juneau.json.*; 036import org.apache.juneau.utils.*; 037 038/** 039 * Generates JSON-schema metadata about POJOs. 040 * 041 * <h5 class='section'>Notes:</h5><ul> 042 * <li class='note'>This class is thread safe and reusable. 043 * </ul> 044 * 045 * <p> 046 * <h5 class='section'>See Also:</h5><ul> 047 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JsonSchemaDetails">JSON-Schema Support</a> 048 * </ul> 049 */ 050public class JsonSchemaGenerator extends BeanTraverseContext implements JsonSchemaMetaProvider { 051 052 //------------------------------------------------------------------------------------------------------------------- 053 // Static 054 //------------------------------------------------------------------------------------------------------------------- 055 056 /** Default serializer, all default settings.*/ 057 public static final JsonSchemaGenerator DEFAULT = new JsonSchemaGenerator(create()); 058 059 /** 060 * Creates a new builder for this object. 061 * 062 * @return A new builder. 063 */ 064 public static Builder create() { 065 return new Builder(); 066 } 067 068 //------------------------------------------------------------------------------------------------------------------- 069 // Builder 070 //------------------------------------------------------------------------------------------------------------------- 071 072 /** 073 * Builder class. 074 */ 075 public static class Builder extends BeanTraverseContext.Builder { 076 077 private static final Cache<HashKey,JsonSchemaGenerator> CACHE = Cache.of(HashKey.class, JsonSchemaGenerator.class).build(); 078 079 final JsonSerializer.Builder jsonSerializerBuilder; 080 final JsonParser.Builder jsonParserBuilder; 081 082 SortedSet<TypeCategory> addDescriptionsTo, addExamplesTo; 083 boolean allowNestedDescriptions, allowNestedExamples, useBeanDefs; 084 Class<? extends BeanDefMapper> beanDefMapper; 085 SortedSet<String> ignoreTypes; 086 087 /** 088 * Constructor, default settings. 089 */ 090 protected Builder() { 091 BeanContext.Builder bc = beanContext(); 092 jsonSerializerBuilder = JsonSerializer.create().beanContext(bc); 093 jsonParserBuilder = JsonParser.create().beanContext(bc); 094 registerBuilders(jsonSerializerBuilder, jsonParserBuilder); 095 addDescriptionsTo = null; 096 addExamplesTo = null; 097 allowNestedDescriptions = env("JsonSchemaGenerator.allowNestedDescriptions", false); 098 allowNestedExamples = env("JsonSchemaGenerator.allowNestedExamples", false); 099 useBeanDefs = env("JsonSchemaGenerator.useBeanDefs", false); 100 beanDefMapper = BasicBeanDefMapper.class; 101 ignoreTypes = null; 102 } 103 104 /** 105 * Copy constructor. 106 * 107 * @param copyFrom The bean to copy from. 108 */ 109 protected Builder(JsonSchemaGenerator copyFrom) { 110 super(copyFrom); 111 BeanContext.Builder bc = beanContext(); 112 jsonSerializerBuilder = copyFrom.jsonSerializer.copy().beanContext(bc); 113 jsonParserBuilder = copyFrom.jsonParser.copy().beanContext(bc); 114 registerBuilders(jsonSerializerBuilder, jsonParserBuilder); 115 addDescriptionsTo = copyFrom.addDescriptionsTo.isEmpty() ? null : new TreeSet<>(copyFrom.addDescriptionsTo); 116 addExamplesTo = copyFrom.addExamplesTo.isEmpty() ? null : new TreeSet<>(copyFrom.addExamplesTo); 117 allowNestedDescriptions = copyFrom.allowNestedDescriptions; 118 allowNestedExamples = copyFrom.allowNestedExamples; 119 useBeanDefs = copyFrom.useBeanDefs; 120 beanDefMapper = copyFrom.beanDefMapper; 121 ignoreTypes = copyFrom.ignoreTypes.isEmpty() ? null : new TreeSet<>(copyFrom.ignoreTypes); 122 } 123 124 /** 125 * Copy constructor. 126 * 127 * @param copyFrom The builder to copy from. 128 */ 129 protected Builder(Builder copyFrom) { 130 super(copyFrom); 131 BeanContext.Builder bc = beanContext(); 132 jsonSerializerBuilder = copyFrom.jsonSerializerBuilder.copy().beanContext(bc); 133 jsonParserBuilder = copyFrom.jsonParserBuilder.copy().beanContext(bc); 134 registerBuilders(jsonSerializerBuilder, jsonParserBuilder); 135 addDescriptionsTo = copyFrom.addDescriptionsTo == null ? null : new TreeSet<>(copyFrom.addDescriptionsTo); 136 addExamplesTo = copyFrom.addExamplesTo == null ? null : new TreeSet<>(copyFrom.addExamplesTo); 137 allowNestedDescriptions = copyFrom.allowNestedDescriptions; 138 allowNestedExamples = copyFrom.allowNestedExamples; 139 useBeanDefs = copyFrom.useBeanDefs; 140 beanDefMapper = copyFrom.beanDefMapper; 141 ignoreTypes = copyFrom.ignoreTypes == null ? null : new TreeSet<>(copyFrom.ignoreTypes); 142 } 143 144 @Override /* Context.Builder */ 145 public Builder copy() { 146 return new Builder(this); 147 } 148 149 @Override /* Context.Builder */ 150 public JsonSchemaGenerator build() { 151 return cache(CACHE).build(JsonSchemaGenerator.class); 152 } 153 154 @Override /* Context.Builder */ 155 public HashKey hashKey() { 156 return HashKey.of( 157 super.hashKey(), 158 jsonSerializerBuilder.hashKey(), 159 jsonParserBuilder.hashKey(), 160 addDescriptionsTo, 161 addExamplesTo, 162 allowNestedDescriptions, 163 allowNestedExamples, 164 useBeanDefs, 165 beanDefMapper, 166 ignoreTypes 167 ); 168 } 169 170 //----------------------------------------------------------------------------------------------------------------- 171 // Properties 172 //----------------------------------------------------------------------------------------------------------------- 173 174 /** 175 * Add descriptions. 176 * 177 * <p> 178 * Identifies which categories of types that descriptions should be automatically added to generated schemas. 179 * The description is the result of calling {@link ClassMeta#getFullName()}. 180 * The format is a comma-delimited list of any of the following values: 181 * 182 * <ul class='javatree'> 183 * <li class='jf'>{@link TypeCategory#BEAN BEAN} 184 * <li class='jf'>{@link TypeCategory#COLLECTION COLLECTION} 185 * <li class='jf'>{@link TypeCategory#ARRAY ARRAY} 186 * <li class='jf'>{@link TypeCategory#MAP MAP} 187 * <li class='jf'>{@link TypeCategory#STRING STRING} 188 * <li class='jf'>{@link TypeCategory#NUMBER NUMBER} 189 * <li class='jf'>{@link TypeCategory#BOOLEAN BOOLEAN} 190 * <li class='jf'>{@link TypeCategory#ANY ANY} 191 * <li class='jf'>{@link TypeCategory#OTHER OTHER} 192 * </ul> 193 * 194 * @param values 195 * The values to add to this setting. 196 * <br>The default is an empty string. 197 * @return This object. 198 */ 199 public Builder addDescriptionsTo(TypeCategory...values) { 200 addDescriptionsTo = addAll(addDescriptionsTo, values); 201 return this; 202 } 203 204 /** 205 * Add examples. 206 * 207 * <p> 208 * Identifies which categories of types that examples should be automatically added to generated schemas. 209 * <p> 210 * The examples come from calling {@link ClassMeta#getExample(BeanSession,JsonParserSession)} which in turn gets examples 211 * from the following: 212 * <ul class='javatree'> 213 * <li class='ja'>{@link Example} 214 * <li class='ja'>{@link Marshalled#example() Marshalled(example)} 215 * </ul> 216 * 217 * <p> 218 * The format is a comma-delimited list of any of the following values: 219 * 220 * <ul class='javatree'> 221 * <li class='jf'>{@link TypeCategory#BEAN BEAN} 222 * <li class='jf'>{@link TypeCategory#COLLECTION COLLECTION} 223 * <li class='jf'>{@link TypeCategory#ARRAY ARRAY} 224 * <li class='jf'>{@link TypeCategory#MAP MAP} 225 * <li class='jf'>{@link TypeCategory#STRING STRING} 226 * <li class='jf'>{@link TypeCategory#NUMBER NUMBER} 227 * <li class='jf'>{@link TypeCategory#BOOLEAN BOOLEAN} 228 * <li class='jf'>{@link TypeCategory#ANY ANY} 229 * <li class='jf'>{@link TypeCategory#OTHER OTHER} 230 * </ul> 231 * 232 * @param values 233 * The values to add to this setting. 234 * <br>The default is an empty string. 235 * @return This object. 236 */ 237 public Builder addExamplesTo(TypeCategory...values) { 238 addExamplesTo = addAll(addExamplesTo, values); 239 return this; 240 } 241 242 /** 243 * Allow nested descriptions. 244 * 245 * <p> 246 * Identifies whether nested descriptions are allowed in schema definitions. 247 * 248 * @return This object. 249 */ 250 public Builder allowNestedDescriptions() { 251 return allowNestedDescriptions(true); 252 } 253 254 /** 255 * Same as {@link #allowNestedDescriptions()} but allows you to explicitly specify the value. 256 * 257 * @param value The value for this setting. 258 * @return This object. 259 */ 260 public Builder allowNestedDescriptions(boolean value) { 261 allowNestedDescriptions = value; 262 return this; 263 } 264 265 /** 266 * Allow nested examples. 267 * 268 * <p> 269 * Identifies whether nested examples are allowed in schema definitions. 270 * 271 * @return This object. 272 */ 273 public Builder allowNestedExamples() { 274 return allowNestedExamples(true); 275 } 276 277 /** 278 * Same as {@link #allowNestedExamples()} but allows you to explicitly specify the value. 279 * 280 * @param value The value for this setting. 281 * @return This object. 282 */ 283 public Builder allowNestedExamples(boolean value) { 284 allowNestedExamples = value; 285 return this; 286 } 287 288 /** 289 * Schema definition mapper. 290 * 291 * <p> 292 * Interface to use for converting Bean classes to definition IDs and URIs. 293 * <p> 294 * Used primarily for defining common definition sections for beans in Swagger JSON. 295 * <p> 296 * This setting is ignored if {@link JsonSchemaGenerator.Builder#useBeanDefs()} is not enabled. 297 * 298 * @param value 299 * The new value for this setting. 300 * <br>The default is {@link org.apache.juneau.jsonschema.BasicBeanDefMapper}. 301 * @return This object. 302 */ 303 public Builder beanDefMapper(Class<? extends BeanDefMapper> value) { 304 beanDefMapper = value; 305 return this; 306 } 307 308 /** 309 * Ignore types from schema definitions. 310 * 311 * <h5 class='section'>Description:</h5> 312 * <p> 313 * Defines class name patterns that should be ignored when generating schema definitions in the generated 314 * Swagger documentation. 315 * 316 * <h5 class='section'>Example:</h5> 317 * <p class='bjava'> 318 * <jc>// Don't generate schema for any prototype packages or the class named 'Swagger'.</jc> 319 * <ja>@JsonSchemaConfig</ja>( 320 * ignoreTypes=<js>"Swagger,*.proto.*"</js> 321 * ) 322 * <jk>public class</jk> MyResource {...} 323 * </p> 324 * 325 * @param values 326 * The values to add. 327 * @return This object. 328 */ 329 public Builder ignoreTypes(String...values) { 330 ignoreTypes = addAll(ignoreTypes, values); 331 return this; 332 } 333 334 /** 335 * Use bean definitions. 336 * 337 * <p> 338 * When enabled, schemas on beans will be serialized as the following: 339 * <p class='bjson'> 340 * { 341 * type: <js>'object'</js>, 342 * <js>'$ref'</js>: <js>'#/definitions/TypeId'</js> 343 * } 344 * </p> 345 * 346 * <p> 347 * The definitions can then be retrieved from the session using {@link JsonSchemaGeneratorSession#getBeanDefs()}. 348 * <p> 349 * Definitions can also be added programmatically using {@link JsonSchemaGeneratorSession#addBeanDef(String, JsonMap)}. 350 * 351 * @return This object. 352 */ 353 public Builder useBeanDefs() { 354 return useBeanDefs(true); 355 } 356 357 /** 358 * Same as {@link #useBeanDefs()} but allows you to explicitly specify the value. 359 * 360 * @param value The value for this setting. 361 * @return This object. 362 */ 363 public Builder useBeanDefs(boolean value) { 364 useBeanDefs = value; 365 return this; 366 } 367 368 /** 369 * Gives access to the inner JSON serializer builder if you want to modify the serializer settings. 370 * 371 * @return The JSON serializer builder. 372 */ 373 public JsonSerializer.Builder getJsonSerializerBuilder() { 374 return jsonSerializerBuilder; 375 } 376 377 /** 378 * Gives access to the inner JSON parser builder if you want to modify the parser settings. 379 * 380 * @return The JSON serializer builder. 381 */ 382 public JsonParser.Builder getJsonParserBuilder() { 383 return jsonParserBuilder; 384 } 385 @Override /* Overridden from Builder */ 386 public Builder annotations(Annotation...values) { 387 super.annotations(values); 388 return this; 389 } 390 391 @Override /* Overridden from Builder */ 392 public Builder apply(AnnotationWorkList work) { 393 super.apply(work); 394 return this; 395 } 396 397 @Override /* Overridden from Builder */ 398 public Builder applyAnnotations(Object...from) { 399 super.applyAnnotations(from); 400 return this; 401 } 402 403 @Override /* Overridden from Builder */ 404 public Builder applyAnnotations(Class<?>...from) { 405 super.applyAnnotations(from); 406 return this; 407 } 408 409 @Override /* Overridden from Builder */ 410 public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) { 411 super.cache(value); 412 return this; 413 } 414 415 @Override /* Overridden from Builder */ 416 public Builder debug() { 417 super.debug(); 418 return this; 419 } 420 421 @Override /* Overridden from Builder */ 422 public Builder debug(boolean value) { 423 super.debug(value); 424 return this; 425 } 426 427 @Override /* Overridden from Builder */ 428 public Builder impl(Context value) { 429 super.impl(value); 430 return this; 431 } 432 433 @Override /* Overridden from Builder */ 434 public Builder type(Class<? extends org.apache.juneau.Context> value) { 435 super.type(value); 436 return this; 437 } 438 439 @Override /* Overridden from Builder */ 440 public Builder beanClassVisibility(Visibility value) { 441 super.beanClassVisibility(value); 442 return this; 443 } 444 445 @Override /* Overridden from Builder */ 446 public Builder beanConstructorVisibility(Visibility value) { 447 super.beanConstructorVisibility(value); 448 return this; 449 } 450 451 @Override /* Overridden from Builder */ 452 public Builder beanContext(BeanContext value) { 453 super.beanContext(value); 454 return this; 455 } 456 457 @Override /* Overridden from Builder */ 458 public Builder beanContext(BeanContext.Builder value) { 459 super.beanContext(value); 460 return this; 461 } 462 463 @Override /* Overridden from Builder */ 464 public Builder beanDictionary(java.lang.Class<?>...values) { 465 super.beanDictionary(values); 466 return this; 467 } 468 469 @Override /* Overridden from Builder */ 470 public Builder beanFieldVisibility(Visibility value) { 471 super.beanFieldVisibility(value); 472 return this; 473 } 474 475 @Override /* Overridden from Builder */ 476 public Builder beanInterceptor(Class<?> on, Class<? extends org.apache.juneau.swap.BeanInterceptor<?>> value) { 477 super.beanInterceptor(on, value); 478 return this; 479 } 480 481 @Override /* Overridden from Builder */ 482 public Builder beanMapPutReturnsOldValue() { 483 super.beanMapPutReturnsOldValue(); 484 return this; 485 } 486 487 @Override /* Overridden from Builder */ 488 public Builder beanMethodVisibility(Visibility value) { 489 super.beanMethodVisibility(value); 490 return this; 491 } 492 493 @Override /* Overridden from Builder */ 494 public Builder beanProperties(Map<String,Object> values) { 495 super.beanProperties(values); 496 return this; 497 } 498 499 @Override /* Overridden from Builder */ 500 public Builder beanProperties(Class<?> beanClass, String properties) { 501 super.beanProperties(beanClass, properties); 502 return this; 503 } 504 505 @Override /* Overridden from Builder */ 506 public Builder beanProperties(String beanClassName, String properties) { 507 super.beanProperties(beanClassName, properties); 508 return this; 509 } 510 511 @Override /* Overridden from Builder */ 512 public Builder beanPropertiesExcludes(Map<String,Object> values) { 513 super.beanPropertiesExcludes(values); 514 return this; 515 } 516 517 @Override /* Overridden from Builder */ 518 public Builder beanPropertiesExcludes(Class<?> beanClass, String properties) { 519 super.beanPropertiesExcludes(beanClass, properties); 520 return this; 521 } 522 523 @Override /* Overridden from Builder */ 524 public Builder beanPropertiesExcludes(String beanClassName, String properties) { 525 super.beanPropertiesExcludes(beanClassName, properties); 526 return this; 527 } 528 529 @Override /* Overridden from Builder */ 530 public Builder beanPropertiesReadOnly(Map<String,Object> values) { 531 super.beanPropertiesReadOnly(values); 532 return this; 533 } 534 535 @Override /* Overridden from Builder */ 536 public Builder beanPropertiesReadOnly(Class<?> beanClass, String properties) { 537 super.beanPropertiesReadOnly(beanClass, properties); 538 return this; 539 } 540 541 @Override /* Overridden from Builder */ 542 public Builder beanPropertiesReadOnly(String beanClassName, String properties) { 543 super.beanPropertiesReadOnly(beanClassName, properties); 544 return this; 545 } 546 547 @Override /* Overridden from Builder */ 548 public Builder beanPropertiesWriteOnly(Map<String,Object> values) { 549 super.beanPropertiesWriteOnly(values); 550 return this; 551 } 552 553 @Override /* Overridden from Builder */ 554 public Builder beanPropertiesWriteOnly(Class<?> beanClass, String properties) { 555 super.beanPropertiesWriteOnly(beanClass, properties); 556 return this; 557 } 558 559 @Override /* Overridden from Builder */ 560 public Builder beanPropertiesWriteOnly(String beanClassName, String properties) { 561 super.beanPropertiesWriteOnly(beanClassName, properties); 562 return this; 563 } 564 565 @Override /* Overridden from Builder */ 566 public Builder beansRequireDefaultConstructor() { 567 super.beansRequireDefaultConstructor(); 568 return this; 569 } 570 571 @Override /* Overridden from Builder */ 572 public Builder beansRequireSerializable() { 573 super.beansRequireSerializable(); 574 return this; 575 } 576 577 @Override /* Overridden from Builder */ 578 public Builder beansRequireSettersForGetters() { 579 super.beansRequireSettersForGetters(); 580 return this; 581 } 582 583 @Override /* Overridden from Builder */ 584 public Builder dictionaryOn(Class<?> on, java.lang.Class<?>...values) { 585 super.dictionaryOn(on, values); 586 return this; 587 } 588 589 @Override /* Overridden from Builder */ 590 public Builder disableBeansRequireSomeProperties() { 591 super.disableBeansRequireSomeProperties(); 592 return this; 593 } 594 595 @Override /* Overridden from Builder */ 596 public Builder disableIgnoreMissingSetters() { 597 super.disableIgnoreMissingSetters(); 598 return this; 599 } 600 601 @Override /* Overridden from Builder */ 602 public Builder disableIgnoreTransientFields() { 603 super.disableIgnoreTransientFields(); 604 return this; 605 } 606 607 @Override /* Overridden from Builder */ 608 public Builder disableIgnoreUnknownNullBeanProperties() { 609 super.disableIgnoreUnknownNullBeanProperties(); 610 return this; 611 } 612 613 @Override /* Overridden from Builder */ 614 public Builder disableInterfaceProxies() { 615 super.disableInterfaceProxies(); 616 return this; 617 } 618 619 @Override /* Overridden from Builder */ 620 public <T> Builder example(Class<T> pojoClass, T o) { 621 super.example(pojoClass, o); 622 return this; 623 } 624 625 @Override /* Overridden from Builder */ 626 public <T> Builder example(Class<T> pojoClass, String json) { 627 super.example(pojoClass, json); 628 return this; 629 } 630 631 @Override /* Overridden from Builder */ 632 public Builder findFluentSetters() { 633 super.findFluentSetters(); 634 return this; 635 } 636 637 @Override /* Overridden from Builder */ 638 public Builder findFluentSetters(Class<?> on) { 639 super.findFluentSetters(on); 640 return this; 641 } 642 643 @Override /* Overridden from Builder */ 644 public Builder ignoreInvocationExceptionsOnGetters() { 645 super.ignoreInvocationExceptionsOnGetters(); 646 return this; 647 } 648 649 @Override /* Overridden from Builder */ 650 public Builder ignoreInvocationExceptionsOnSetters() { 651 super.ignoreInvocationExceptionsOnSetters(); 652 return this; 653 } 654 655 @Override /* Overridden from Builder */ 656 public Builder ignoreUnknownBeanProperties() { 657 super.ignoreUnknownBeanProperties(); 658 return this; 659 } 660 661 @Override /* Overridden from Builder */ 662 public Builder ignoreUnknownEnumValues() { 663 super.ignoreUnknownEnumValues(); 664 return this; 665 } 666 667 @Override /* Overridden from Builder */ 668 public Builder implClass(Class<?> interfaceClass, Class<?> implClass) { 669 super.implClass(interfaceClass, implClass); 670 return this; 671 } 672 673 @Override /* Overridden from Builder */ 674 public Builder implClasses(Map<Class<?>,Class<?>> values) { 675 super.implClasses(values); 676 return this; 677 } 678 679 @Override /* Overridden from Builder */ 680 public Builder interfaceClass(Class<?> on, Class<?> value) { 681 super.interfaceClass(on, value); 682 return this; 683 } 684 685 @Override /* Overridden from Builder */ 686 public Builder interfaces(java.lang.Class<?>...value) { 687 super.interfaces(value); 688 return this; 689 } 690 691 @Override /* Overridden from Builder */ 692 public Builder locale(Locale value) { 693 super.locale(value); 694 return this; 695 } 696 697 @Override /* Overridden from Builder */ 698 public Builder mediaType(MediaType value) { 699 super.mediaType(value); 700 return this; 701 } 702 703 @Override /* Overridden from Builder */ 704 public Builder notBeanClasses(java.lang.Class<?>...values) { 705 super.notBeanClasses(values); 706 return this; 707 } 708 709 @Override /* Overridden from Builder */ 710 public Builder notBeanPackages(String...values) { 711 super.notBeanPackages(values); 712 return this; 713 } 714 715 @Override /* Overridden from Builder */ 716 public Builder propertyNamer(Class<? extends org.apache.juneau.PropertyNamer> value) { 717 super.propertyNamer(value); 718 return this; 719 } 720 721 @Override /* Overridden from Builder */ 722 public Builder propertyNamer(Class<?> on, Class<? extends org.apache.juneau.PropertyNamer> value) { 723 super.propertyNamer(on, value); 724 return this; 725 } 726 727 @Override /* Overridden from Builder */ 728 public Builder sortProperties() { 729 super.sortProperties(); 730 return this; 731 } 732 733 @Override /* Overridden from Builder */ 734 public Builder sortProperties(java.lang.Class<?>...on) { 735 super.sortProperties(on); 736 return this; 737 } 738 739 @Override /* Overridden from Builder */ 740 public Builder stopClass(Class<?> on, Class<?> value) { 741 super.stopClass(on, value); 742 return this; 743 } 744 745 @Override /* Overridden from Builder */ 746 public <T, S> Builder swap(Class<T> normalClass, Class<S> swappedClass, ThrowingFunction<T,S> swapFunction) { 747 super.swap(normalClass, swappedClass, swapFunction); 748 return this; 749 } 750 751 @Override /* Overridden from Builder */ 752 public <T, S> Builder swap(Class<T> normalClass, Class<S> swappedClass, ThrowingFunction<T,S> swapFunction, ThrowingFunction<S,T> unswapFunction) { 753 super.swap(normalClass, swappedClass, swapFunction, unswapFunction); 754 return this; 755 } 756 757 @Override /* Overridden from Builder */ 758 public Builder swaps(Object...values) { 759 super.swaps(values); 760 return this; 761 } 762 763 @Override /* Overridden from Builder */ 764 public Builder swaps(Class<?>...values) { 765 super.swaps(values); 766 return this; 767 } 768 769 @Override /* Overridden from Builder */ 770 public Builder timeZone(TimeZone value) { 771 super.timeZone(value); 772 return this; 773 } 774 775 @Override /* Overridden from Builder */ 776 public Builder typeName(Class<?> on, String value) { 777 super.typeName(on, value); 778 return this; 779 } 780 781 @Override /* Overridden from Builder */ 782 public Builder typePropertyName(String value) { 783 super.typePropertyName(value); 784 return this; 785 } 786 787 @Override /* Overridden from Builder */ 788 public Builder typePropertyName(Class<?> on, String value) { 789 super.typePropertyName(on, value); 790 return this; 791 } 792 793 @Override /* Overridden from Builder */ 794 public Builder useEnumNames() { 795 super.useEnumNames(); 796 return this; 797 } 798 799 @Override /* Overridden from Builder */ 800 public Builder useJavaBeanIntrospector() { 801 super.useJavaBeanIntrospector(); 802 return this; 803 } 804 805 @Override /* Overridden from Builder */ 806 public Builder detectRecursions() { 807 super.detectRecursions(); 808 return this; 809 } 810 811 @Override /* Overridden from Builder */ 812 public Builder detectRecursions(boolean value) { 813 super.detectRecursions(value); 814 return this; 815 } 816 817 @Override /* Overridden from Builder */ 818 public Builder ignoreRecursions() { 819 super.ignoreRecursions(); 820 return this; 821 } 822 823 @Override /* Overridden from Builder */ 824 public Builder ignoreRecursions(boolean value) { 825 super.ignoreRecursions(value); 826 return this; 827 } 828 829 @Override /* Overridden from Builder */ 830 public Builder initialDepth(int value) { 831 super.initialDepth(value); 832 return this; 833 } 834 835 @Override /* Overridden from Builder */ 836 public Builder maxDepth(int value) { 837 super.maxDepth(value); 838 return this; 839 } 840 } 841 842 //------------------------------------------------------------------------------------------------------------------- 843 // Instance 844 //------------------------------------------------------------------------------------------------------------------- 845 846 final boolean useBeanDefs, allowNestedExamples, allowNestedDescriptions; 847 final Set<TypeCategory> addExamplesTo, addDescriptionsTo; 848 final Class<? extends BeanDefMapper> beanDefMapper; 849 final Set<String> ignoreTypes; 850 851 private final BeanDefMapper beanDefMapperBean; 852 final JsonSerializer jsonSerializer; 853 final JsonParser jsonParser; 854 private final Pattern[] ignoreTypePatterns; 855 private final Map<ClassMeta<?>,JsonSchemaClassMeta> jsonSchemaClassMetas = new ConcurrentHashMap<>(); 856 private final Map<BeanPropertyMeta,JsonSchemaBeanPropertyMeta> jsonSchemaBeanPropertyMetas = new ConcurrentHashMap<>(); 857 858 /** 859 * Constructor. 860 * 861 * @param builder The builder for this object. 862 */ 863 public JsonSchemaGenerator(Builder builder) { 864 super(builder.detectRecursions().ignoreRecursions()); 865 866 useBeanDefs = builder.useBeanDefs; 867 allowNestedExamples = builder.allowNestedExamples; 868 allowNestedDescriptions = builder.allowNestedDescriptions; 869 beanDefMapper = builder.beanDefMapper; 870 addExamplesTo = builder.addExamplesTo == null ? emptySet() : new TreeSet<>(builder.addExamplesTo); 871 addDescriptionsTo = builder.addDescriptionsTo == null ? emptySet() : new TreeSet<>(builder.addDescriptionsTo); 872 ignoreTypes = builder.ignoreTypes == null ? emptySet() : new TreeSet<>(builder.ignoreTypes); 873 874 Set<Pattern> ignoreTypePatterns = Utils.set(); 875 ignoreTypes.forEach(y -> Utils.split(y, x -> ignoreTypePatterns.add(Pattern.compile(x.replace(".", "\\.").replace("*", ".*"))))); 876 this.ignoreTypePatterns = ignoreTypePatterns.toArray(new Pattern[ignoreTypePatterns.size()]); 877 878 try { 879 beanDefMapperBean = beanDefMapper.getDeclaredConstructor().newInstance(); 880 } catch (Exception e) { 881 throw asRuntimeException(e); 882 } 883 884 jsonSerializer = builder.jsonSerializerBuilder.build(); 885 jsonParser = builder.jsonParserBuilder.beanContext(getBeanContext()).build(); 886 } 887 888 @Override /* Context */ 889 public Builder copy() { 890 return new Builder(this); 891 } 892 893 @Override /* Context */ 894 public JsonSchemaGeneratorSession.Builder createSession() { 895 return JsonSchemaGeneratorSession.create(this); 896 } 897 898 @Override /* Context */ 899 public JsonSchemaGeneratorSession getSession() { 900 return createSession().build(); 901 } 902 903 JsonSerializer getJsonSerializer() { 904 return jsonSerializer; 905 } 906 907 JsonParser getJsonParser() { 908 return jsonParser; 909 } 910 911 //----------------------------------------------------------------------------------------------------------------- 912 // Properties 913 //----------------------------------------------------------------------------------------------------------------- 914 915 /** 916 * Add descriptions to types. 917 * 918 * @see Builder#addDescriptionsTo(TypeCategory...) 919 * @return 920 * Set of categories of types that descriptions should be automatically added to generated schemas. 921 */ 922 protected final Set<TypeCategory> getAddDescriptionsTo() { 923 return addDescriptionsTo; 924 } 925 926 /** 927 * Add examples. 928 * 929 * @see Builder#addExamplesTo(TypeCategory...) 930 * @return 931 * Set of categories of types that examples should be automatically added to generated schemas. 932 */ 933 protected final Set<TypeCategory> getAddExamplesTo() { 934 return addExamplesTo; 935 } 936 937 /** 938 * Allow nested descriptions. 939 * 940 * @see Builder#allowNestedDescriptions() 941 * @return 942 * <jk>true</jk> if nested descriptions are allowed in schema definitions. 943 */ 944 protected final boolean isAllowNestedDescriptions() { 945 return allowNestedDescriptions; 946 } 947 948 /** 949 * Allow nested examples. 950 * 951 * @see Builder#allowNestedExamples() 952 * @return 953 * <jk>true</jk> if nested examples are allowed in schema definitions. 954 */ 955 protected final boolean isAllowNestedExamples() { 956 return allowNestedExamples; 957 } 958 959 /** 960 * Bean schema definition mapper. 961 * 962 * @see Builder#beanDefMapper(Class) 963 * @return 964 * Interface to use for converting Bean classes to definition IDs and URIs. 965 */ 966 protected final BeanDefMapper getBeanDefMapper() { 967 return beanDefMapperBean; 968 } 969 970 /** 971 * Ignore types from schema definitions. 972 * 973 * @see Builder#ignoreTypes(String...) 974 * @return 975 * Custom schema information for particular class types. 976 */ 977 public List<Pattern> getIgnoreTypes() { 978 return Utils.alist(ignoreTypePatterns); 979 } 980 981 /** 982 * Use bean definitions. 983 * 984 * @see Builder#useBeanDefs() 985 * @return 986 * <jk>true</jk> if schemas on beans will be serialized with <js>'$ref'</js> tags. 987 */ 988 protected final boolean isUseBeanDefs() { 989 return useBeanDefs; 990 } 991 992 //----------------------------------------------------------------------------------------------------------------- 993 // Extended metadata 994 //----------------------------------------------------------------------------------------------------------------- 995 996 @Override 997 public JsonSchemaClassMeta getJsonSchemaClassMeta(ClassMeta<?> cm) { 998 JsonSchemaClassMeta m = jsonSchemaClassMetas.get(cm); 999 if (m == null) { 1000 m = new JsonSchemaClassMeta(cm, this); 1001 jsonSchemaClassMetas.put(cm, m); 1002 } 1003 return m; 1004 } 1005 1006 @Override 1007 public JsonSchemaBeanPropertyMeta getJsonSchemaBeanPropertyMeta(BeanPropertyMeta bpm) { 1008 JsonSchemaBeanPropertyMeta m = jsonSchemaBeanPropertyMetas.get(bpm); 1009 if (m == null) { 1010 m = new JsonSchemaBeanPropertyMeta(bpm, this); 1011 jsonSchemaBeanPropertyMetas.put(bpm, m); 1012 } 1013 return m; 1014 } 1015 1016 //----------------------------------------------------------------------------------------------------------------- 1017 // Other methods 1018 //----------------------------------------------------------------------------------------------------------------- 1019 1020 /** 1021 * Returns <jk>true</jk> if the specified type is ignored. 1022 * 1023 * <p> 1024 * The type is ignored if it's specified in the {@link Builder#ignoreTypes(String...)} setting. 1025 * <br>Ignored types return <jk>null</jk> on the call to {@link JsonSchemaGeneratorSession#getSchema(ClassMeta)}. 1026 * 1027 * @param cm The type to check. 1028 * @return <jk>true</jk> if the specified type is ignored. 1029 */ 1030 public boolean isIgnoredType(ClassMeta<?> cm) { 1031 for (Pattern p : ignoreTypePatterns) 1032 if (p.matcher(cm.getSimpleName()).matches() || p.matcher(cm.getName()).matches()) 1033 return true; 1034 return false; 1035 } 1036 1037 //----------------------------------------------------------------------------------------------------------------- 1038 // Other methods 1039 //----------------------------------------------------------------------------------------------------------------- 1040 1041 @Override /* Context */ 1042 protected JsonMap properties() { 1043 return filteredMap() 1044 .append("useBeanDefs", useBeanDefs) 1045 .append("allowNestedExamples", allowNestedExamples) 1046 .append("allowNestedDescriptions", allowNestedDescriptions) 1047 .append("beanDefMapper", beanDefMapper) 1048 .append("addExamplesTo", addExamplesTo) 1049 .append("addDescriptionsTo", addDescriptionsTo) 1050 .append("ignoreTypes", ignoreTypes); 1051 } 1052}