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