1 package io.swagger.v3.core.jackson;
2
3 import com.fasterxml.jackson.annotation.JsonIdentityInfo;
4 import com.fasterxml.jackson.annotation.JsonIdentityReference;
5 import com.fasterxml.jackson.annotation.JsonIgnore;
6 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
7 import com.fasterxml.jackson.annotation.JsonProperty;
8 import com.fasterxml.jackson.annotation.JsonTypeInfo;
9 import com.fasterxml.jackson.annotation.JsonUnwrapped;
10 import com.fasterxml.jackson.annotation.JsonView;
11 import com.fasterxml.jackson.annotation.ObjectIdGenerator;
12 import com.fasterxml.jackson.annotation.ObjectIdGenerators;
13 import com.fasterxml.jackson.databind.AnnotationIntrospector;
14 import com.fasterxml.jackson.databind.BeanDescription;
15 import com.fasterxml.jackson.databind.JavaType;
16 import com.fasterxml.jackson.databind.ObjectMapper;
17 import com.fasterxml.jackson.databind.PropertyMetadata;
18 import com.fasterxml.jackson.databind.SerializationFeature;
19 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
20 import com.fasterxml.jackson.databind.introspect.Annotated;
21 import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
22 import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
23 import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
24 import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
25 import com.fasterxml.jackson.databind.introspect.POJOPropertyBuilder;
26 import com.fasterxml.jackson.databind.jsontype.NamedType;
27 import io.swagger.v3.core.converter.AnnotatedType;
28 import io.swagger.v3.core.converter.ModelConverter;
29 import io.swagger.v3.core.converter.ModelConverterContext;
30 import io.swagger.v3.core.util.AnnotationsUtils;
31 import io.swagger.v3.core.util.Constants;
32 import io.swagger.v3.core.util.Json;
33 import io.swagger.v3.core.util.ObjectMapperFactory;
34 import io.swagger.v3.core.util.PrimitiveType;
35 import io.swagger.v3.core.util.ReflectionUtils;
36 import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
37 import io.swagger.v3.oas.models.ExternalDocumentation;
38 import io.swagger.v3.oas.models.media.ArraySchema;
39 import io.swagger.v3.oas.models.media.ComposedSchema;
40 import io.swagger.v3.oas.models.media.Discriminator;
41 import io.swagger.v3.oas.models.media.IntegerSchema;
42 import io.swagger.v3.oas.models.media.MapSchema;
43 import io.swagger.v3.oas.models.media.NumberSchema;
44 import io.swagger.v3.oas.models.media.ObjectSchema;
45 import io.swagger.v3.oas.models.media.Schema;
46 import io.swagger.v3.oas.models.media.StringSchema;
47 import io.swagger.v3.oas.models.media.UUIDSchema;
48 import io.swagger.v3.oas.models.media.XML;
49 import org.apache.commons.lang3.StringUtils;
50 import org.apache.commons.lang3.math.NumberUtils;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 import javax.validation.constraints.DecimalMax;
55 import javax.validation.constraints.DecimalMin;
56 import javax.validation.constraints.Max;
57 import javax.validation.constraints.Min;
58 import javax.validation.constraints.Pattern;
59 import javax.validation.constraints.Size;
60 import javax.xml.bind.annotation.XmlAccessType;
61 import javax.xml.bind.annotation.XmlAccessorType;
62 import javax.xml.bind.annotation.XmlAttribute;
63 import javax.xml.bind.annotation.XmlElement;
64 import javax.xml.bind.annotation.XmlElementRef;
65 import javax.xml.bind.annotation.XmlElementRefs;
66 import javax.xml.bind.annotation.XmlRootElement;
67 import java.io.IOException;
68 import java.lang.annotation.Annotation;
69 import java.lang.reflect.InvocationTargetException;
70 import java.lang.reflect.Method;
71 import java.lang.reflect.Type;
72 import java.math.BigDecimal;
73 import java.util.ArrayList;
74 import java.util.Arrays;
75 import java.util.Collection;
76 import java.util.Collections;
77 import java.util.HashMap;
78 import java.util.HashSet;
79 import java.util.Iterator;
80 import java.util.LinkedHashMap;
81 import java.util.List;
82 import java.util.Map;
83 import java.util.Set;
84 import java.util.stream.Collectors;
85 import java.util.stream.Stream;
86
87 import static io.swagger.v3.core.util.RefUtils.constructRef;
88
89 public class ModelResolver extends AbstractModelConverter implements ModelConverter {
90     Logger LOGGER = LoggerFactory.getLogger(ModelResolver.class);
91
92     public static final String SET_PROPERTY_OF_COMPOSED_MODEL_AS_SIBLING = "composed-model-properties-as-sibiling";
93     public static final String SET_PROPERTY_OF_ENUMS_AS_REF = "enums-as-ref";
94
95     public static boolean composedModelPropertiesAsSibling = System.getProperty(SET_PROPERTY_OF_COMPOSED_MODEL_AS_SIBLING) != null ? true : false;
96
97     /**
98      * Allows all enums to be resolved as a reference to a scheme added to the components section.
99      */

100     public static boolean enumsAsRef = System.getProperty(SET_PROPERTY_OF_ENUMS_AS_REF) != null ? true : false;
101
102     public ModelResolver(ObjectMapper mapper) {
103         super(mapper);
104     }
105     public ModelResolver(ObjectMapper mapper, TypeNameResolver typeNameResolver) {
106         super(mapper, typeNameResolver);
107     }
108
109     public ObjectMapper objectMapper() {
110         return _mapper;
111     }
112
113     @Override
114     public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context, Iterator<ModelConverter> next) {
115
116         boolean isPrimitive = false;
117         Schema model = null;
118
119         if (annotatedType == null) {
120             return null;
121         }
122         if (this.shouldIgnoreClass(annotatedType.getType())) {
123             return null;
124         }
125
126         final JavaType type;
127         if (annotatedType.getType() instanceof JavaType) {
128             type = (JavaType) annotatedType.getType();
129         } else {
130             type = _mapper.constructType(annotatedType.getType());
131         }
132
133         final Annotation resolvedSchemaOrArrayAnnotation = AnnotationsUtils.mergeSchemaAnnotations(annotatedType.getCtxAnnotations(), type);
134         final io.swagger.v3.oas.annotations.media.Schema resolvedSchemaAnnotation =
135                 resolvedSchemaOrArrayAnnotation == null ?
136                         null :
137                         resolvedSchemaOrArrayAnnotation instanceof io.swagger.v3.oas.annotations.media.ArraySchema ?
138                                 ((io.swagger.v3.oas.annotations.media.ArraySchema) resolvedSchemaOrArrayAnnotation).schema() :
139                                 (io.swagger.v3.oas.annotations.media.Schema) resolvedSchemaOrArrayAnnotation;
140
141         final io.swagger.v3.oas.annotations.media.ArraySchema resolvedArrayAnnotation =
142                 resolvedSchemaOrArrayAnnotation == null ?
143                         null :
144                         resolvedSchemaOrArrayAnnotation instanceof io.swagger.v3.oas.annotations.media.ArraySchema ?
145                                 (io.swagger.v3.oas.annotations.media.ArraySchema) resolvedSchemaOrArrayAnnotation :
146                                 null;
147
148         final BeanDescription beanDesc;
149         {
150             BeanDescription recurBeanDesc = _mapper.getSerializationConfig().introspect(type);
151
152             HashSet<String> visited = new HashSet<>();
153             JsonSerialize jsonSerialize = recurBeanDesc.getClassAnnotations().get(JsonSerialize.class);
154             while (jsonSerialize != null && !Void.class.equals(jsonSerialize.as())) {
155                 String asName = jsonSerialize.as().getName();
156                 if (visited.contains(asName)) break;
157                 visited.add(asName);
158
159                 recurBeanDesc = _mapper.getSerializationConfig().introspect(
160                         _mapper.constructType(jsonSerialize.as())
161                 );
162                 jsonSerialize = recurBeanDesc.getClassAnnotations().get(JsonSerialize.class);
163             }
164             beanDesc = recurBeanDesc;
165         }
166
167
168         String name = annotatedType.getName();
169         if (StringUtils.isBlank(name)) {
170             // allow override of name from annotation
171             if (!annotatedType.isSkipSchemaName() && resolvedSchemaAnnotation != null && !resolvedSchemaAnnotation.name().isEmpty()) {
172                 name = resolvedSchemaAnnotation.name();
173             }
174             if (StringUtils.isBlank(name) && !ReflectionUtils.isSystemType(type)) {
175                 name = _typeName(type, beanDesc);
176             }
177         }
178
179         name = decorateModelName(annotatedType, name);
180
181         // if we have a ref we don't consider anything else
182         if (resolvedSchemaAnnotation != null &&
183                 StringUtils.isNotEmpty(resolvedSchemaAnnotation.ref())) {
184             if (resolvedArrayAnnotation == null) {
185                 return new Schema().$ref(resolvedSchemaAnnotation.ref()).name(name);
186             } else {
187                 ArraySchema schema = new ArraySchema();
188                 resolveArraySchema(annotatedType, schema, resolvedArrayAnnotation);
189                 return schema.items(new Schema().$ref(resolvedSchemaAnnotation.ref()).name(name));
190             }
191         }
192
193         if (!annotatedType.isSkipOverride() && resolvedSchemaAnnotation != null && !Void.class.equals(resolvedSchemaAnnotation.implementation())) {
194             Class<?> cls = resolvedSchemaAnnotation.implementation();
195
196             LOGGER.debug("overriding datatype from {} to {}", type, cls.getName());
197
198             Annotation[] ctxAnnotation = null;
199             if (resolvedArrayAnnotation != null && annotatedType.getCtxAnnotations() != null) {
200                 List<Annotation> annList = new ArrayList<>();
201                 for (Annotation a: annotatedType.getCtxAnnotations()) {
202                     if (!(a instanceof ArraySchema)) {
203                         annList.add(a);
204                     }
205                 }
206                 annList.add(resolvedSchemaAnnotation);
207                 ctxAnnotation = annList.toArray(new Annotation[annList.size()]);
208             } else {
209                 ctxAnnotation = annotatedType.getCtxAnnotations();
210             }
211
212             AnnotatedType aType = new AnnotatedType()
213                     .type(cls)
214                     .ctxAnnotations(ctxAnnotation)
215                     .parent(annotatedType.getParent())
216                     .name(annotatedType.getName())
217                     .resolveAsRef(annotatedType.isResolveAsRef())
218                     .jsonViewAnnotation(annotatedType.getJsonViewAnnotation())
219                     .propertyName(annotatedType.getPropertyName())
220                     .skipOverride(true);
221             if (resolvedArrayAnnotation != null) {
222                 ArraySchema schema = new ArraySchema();
223                 resolveArraySchema(annotatedType, schema, resolvedArrayAnnotation);
224                 Schema innerSchema = null;
225
226                 Schema primitive = PrimitiveType.createProperty(cls);
227                 if (primitive != null) {
228                     innerSchema = primitive;
229                 } else {
230                     innerSchema = context.resolve(aType);
231                     if (innerSchema != null && "object".equals(innerSchema.getType()) && StringUtils.isNotBlank(innerSchema.getName())) {
232                         // create a reference for the items
233                         if (context.getDefinedModels().containsKey(innerSchema.getName())) {
234                             innerSchema = new Schema().$ref(constructRef(innerSchema.getName()));
235                         }
236                     } else if (innerSchema != null && innerSchema.get$ref() != null) {
237                         innerSchema = new Schema().$ref(StringUtils.isNotEmpty(innerSchema.get$ref()) ? innerSchema.get$ref() : innerSchema.getName());
238                     }
239                 }
240                 schema.setItems(innerSchema);
241                 return schema;
242             } else {
243                 Schema implSchema = context.resolve(aType);
244                 if (implSchema != null && aType.isResolveAsRef() && "object".equals(implSchema.getType()) && StringUtils.isNotBlank(implSchema.getName())) {
245                     // create a reference for the items
246                     if (context.getDefinedModels().containsKey(implSchema.getName())) {
247                         implSchema = new Schema().$ref(constructRef(implSchema.getName()));
248                     }
249                 } else if (implSchema != null && implSchema.get$ref() != null) {
250                     implSchema = new Schema().$ref(StringUtils.isNotEmpty(implSchema.get$ref()) ? implSchema.get$ref() : implSchema.getName());
251                 }
252                 return implSchema;
253             }
254         }
255
256         if (model == null && !annotatedType.isSkipOverride() && resolvedSchemaAnnotation != null &&
257                 StringUtils.isNotEmpty(resolvedSchemaAnnotation.type()) &&
258                 !resolvedSchemaAnnotation.type().equals("object")) {
259             PrimitiveType primitiveType = PrimitiveType.fromTypeAndFormat(resolvedSchemaAnnotation.type(), resolvedSchemaAnnotation.format());
260             if (primitiveType == null) {
261                 primitiveType = PrimitiveType.fromType(type);
262             }
263             if (primitiveType == null) {
264                 primitiveType = PrimitiveType.fromName(resolvedSchemaAnnotation.type());
265             }
266             if (primitiveType != null) {
267                 Schema primitive = primitiveType.createProperty();
268                 model = primitive;
269                 isPrimitive = true;
270
271             }
272         }
273
274         if (model == null && type.isEnumType()) {
275             model = new StringSchema();
276             _addEnumProps(type.getRawClass(), model);
277             isPrimitive = true;
278         }
279         if (model == null) {
280             PrimitiveType primitiveType = PrimitiveType.fromType(type);
281             if (primitiveType != null) {
282                 model = PrimitiveType.fromType(type).createProperty();
283                 isPrimitive = true;
284             }
285         }
286
287         if (!annotatedType.isSkipJsonIdentity()) {
288             JsonIdentityInfo jsonIdentityInfo = AnnotationsUtils.getAnnotation(JsonIdentityInfo.class, annotatedType.getCtxAnnotations());
289             if (jsonIdentityInfo == null) {
290                 jsonIdentityInfo = type.getRawClass().getAnnotation(JsonIdentityInfo.class);
291             }
292             if (model == null && jsonIdentityInfo != null) {
293                 JsonIdentityReference jsonIdentityReference = AnnotationsUtils.getAnnotation(JsonIdentityReference.class, annotatedType.getCtxAnnotations());
294                 if (jsonIdentityReference == null) {
295                     jsonIdentityReference = type.getRawClass().getAnnotation(JsonIdentityReference.class);
296                 }
297                 model = GeneratorWrapper.processJsonIdentity(annotatedType, context, _mapper, jsonIdentityInfo, jsonIdentityReference);
298                 if (model != null) {
299                     return model;
300                 }
301             }
302         }
303
304         if (model == null && annotatedType.getJsonUnwrappedHandler() != null) {
305             model = annotatedType.getJsonUnwrappedHandler().apply(annotatedType);
306             if (model == null) {
307                 return null;
308             }
309         }
310
311         if ("Object".equals(name)) {
312             return new Schema();
313         }
314
315         if (isPrimitive) {
316             if (annotatedType.isSchemaProperty()) {
317                 //model.name(name);
318             }
319             XML xml = resolveXml(beanDesc.getClassInfo(), annotatedType.getCtxAnnotations(), resolvedSchemaAnnotation);
320             if (xml != null) {
321                 model.xml(xml);
322             }
323             resolveSchemaMembers(model, annotatedType);
324
325             if (resolvedArrayAnnotation != null) {
326                 ArraySchema schema = new ArraySchema();
327                 resolveArraySchema(annotatedType, schema, resolvedArrayAnnotation);
328                 schema.setItems(model);
329                 return schema;
330             }
331             if (type.isEnumType() && shouldResolveEnumAsRef(resolvedSchemaAnnotation)) {
332                 // Store off the ref and add the enum as a top-level model
333                 context.defineModel(name, model, annotatedType, null);
334                 // Return the model as a ref only property
335                 model = new Schema().$ref(name);
336             }
337             return model;
338         }
339
340         /**
341          * --Preventing parent/child hierarchy creation loops - Comment 1--
342          * Creating a parent model will result in the creation of child models. Creating a child model will result in
343          * the creation of a parent model, as per the second If statement following this comment.
344          *
345          * By checking whether a model has already been resolved (as implemented below), loops of parents creating
346          * children and children creating parents can be short-circuited. This works because currently the
347          * ModelConverterContextImpl will return null for a class that already been processed, but has not yet been
348          * defined. This logic works in conjunction with the early immediate definition of model in the context
349          * implemented later in this method (See "Preventing parent/child hierarchy creation loops - Comment 2") to
350          * prevent such
351          */

352         Schema resolvedModel = context.resolve(annotatedType);
353         if (resolvedModel != null) {
354             if (name != null && name.equals(resolvedModel.getName())) {
355                 return resolvedModel;
356             }
357         }
358
359         Type jsonValueType = findJsonValueType(beanDesc);
360
361         if(jsonValueType != null) {
362             AnnotatedType aType = new AnnotatedType()
363                     .type(jsonValueType)
364                     .parent(annotatedType.getParent())
365                     .name(annotatedType.getName())
366                     .schemaProperty(annotatedType.isSchemaProperty())
367                     .resolveAsRef(annotatedType.isResolveAsRef())
368                     .jsonViewAnnotation(annotatedType.getJsonViewAnnotation())
369                     .propertyName(annotatedType.getPropertyName())
370                     .skipOverride(true);
371             return context.resolve(aType);
372         }
373
374         List<Class<?>> composedSchemaReferencedClasses = getComposedSchemaReferencedClasses(type.getRawClass(), annotatedType.getCtxAnnotations(), resolvedSchemaAnnotation);
375         boolean isComposedSchema = composedSchemaReferencedClasses != null;
376
377         if (type.isContainerType()) {
378             // TODO currently a MapSchema or ArraySchema don't also support composed schema props (oneOf,..)
379             isComposedSchema = false;
380             JavaType keyType = type.getKeyType();
381             JavaType valueType = type.getContentType();
382             String pName = null;
383             if (valueType != null) {
384                 BeanDescription valueTypeBeanDesc = _mapper.getSerializationConfig().introspect(valueType);
385                 pName = _typeName(valueType, valueTypeBeanDesc);
386             }
387             Annotation[] schemaAnnotations = null;
388             if (resolvedSchemaAnnotation != null) {
389                 schemaAnnotations = new Annotation[]{resolvedSchemaAnnotation};
390             }
391             if (keyType != null && valueType != null) {
392                 if (ReflectionUtils.isSystemType(type) && !annotatedType.isSchemaProperty() && !annotatedType.isResolveAsRef()) {
393                     context.resolve(new AnnotatedType().type(valueType).jsonViewAnnotation(annotatedType.getJsonViewAnnotation()));
394                     return null;
395                 }
396                 Schema addPropertiesSchema = context.resolve(
397                         new AnnotatedType()
398                                 .type(valueType)
399                                 .schemaProperty(annotatedType.isSchemaProperty())
400                                 .ctxAnnotations(schemaAnnotations)
401                                 .skipSchemaName(true)
402                                 .resolveAsRef(annotatedType.isResolveAsRef())
403                                 .jsonViewAnnotation(annotatedType.getJsonViewAnnotation())
404                                 .propertyName(annotatedType.getPropertyName())
405                                 .parent(annotatedType.getParent()));
406                 if (addPropertiesSchema != null) {
407                     if (StringUtils.isNotBlank(addPropertiesSchema.getName())) {
408                         pName = addPropertiesSchema.getName();
409                     }
410                     if ("object".equals(addPropertiesSchema.getType()) && pName != null) {
411                         // create a reference for the items
412                         if (context.getDefinedModels().containsKey(pName)) {
413                             addPropertiesSchema = new Schema().$ref(constructRef(pName));
414                         }
415                     } else if (addPropertiesSchema.get$ref() != null) {
416                         addPropertiesSchema = new Schema().$ref(StringUtils.isNotEmpty(addPropertiesSchema.get$ref()) ? addPropertiesSchema.get$ref() : addPropertiesSchema.getName());
417                     }
418                 }
419                 Schema mapModel = new MapSchema().additionalProperties(addPropertiesSchema);
420                 mapModel.name(name);
421                 model = mapModel;
422                 //return model;
423             } else if (valueType != null) {
424                 if (ReflectionUtils.isSystemType(type) && !annotatedType.isSchemaProperty() && !annotatedType.isResolveAsRef()) {
425                     context.resolve(new AnnotatedType().type(valueType).jsonViewAnnotation(annotatedType.getJsonViewAnnotation()));
426                     return null;
427                 }
428                 Schema items = context.resolve(new AnnotatedType()
429                         .type(valueType)
430                         .schemaProperty(annotatedType.isSchemaProperty())
431                         .ctxAnnotations(schemaAnnotations)
432                         .skipSchemaName(true)
433                         .resolveAsRef(annotatedType.isResolveAsRef())
434                         .propertyName(annotatedType.getPropertyName())
435                         .jsonViewAnnotation(annotatedType.getJsonViewAnnotation())
436                         .parent(annotatedType.getParent()));
437
438                 if (items == null) {
439                     return null;
440                 }
441                 if (annotatedType.isSchemaProperty() && annotatedType.getCtxAnnotations() != null && annotatedType.getCtxAnnotations().length > 0) {
442                     if (!"object".equals(items.getType())) {
443                         for (Annotation annotation : annotatedType.getCtxAnnotations()) {
444                             if (annotation instanceof XmlElement) {
445                                 XmlElement xmlElement = (XmlElement) annotation;
446                                 if (xmlElement != null && xmlElement.name() != null && !"".equals(xmlElement.name()) && !"##default".equals(xmlElement.name())) {
447                                     XML xml = items.getXml() != null ? items.getXml() : new XML();
448                                     xml.setName(xmlElement.name());
449                                     items.setXml(xml);
450                                 }
451                             }
452                         }
453                     }
454                 }
455                 if (StringUtils.isNotBlank(items.getName())) {
456                     pName = items.getName();
457                 }
458                 if ("object".equals(items.getType()) && pName != null) {
459                     // create a reference for the items
460                     if (context.getDefinedModels().containsKey(pName)) {
461                         items = new Schema().$ref(constructRef(pName));
462                     }
463                 } else if (items.get$ref() != null) {
464                     items = new Schema().$ref(StringUtils.isNotEmpty(items.get$ref()) ? items.get$ref() : items.getName());
465                 }
466
467                 Schema arrayModel =
468                         new ArraySchema().items(items);
469                 if (_isSetType(type.getRawClass())) {
470                     arrayModel.setUniqueItems(true);
471                 }
472                 arrayModel.name(name);
473                 model = arrayModel;
474             } else {
475                 if (ReflectionUtils.isSystemType(type) && !annotatedType.isSchemaProperty() && !annotatedType.isResolveAsRef()) {
476                     return null;
477                 }
478             }
479         } else if (isComposedSchema) {
480             model = new ComposedSchema()
481                     .type("object")
482                     .name(name);
483         } else {
484             if (_isOptionalType(type)) {
485                 AnnotatedType aType = new AnnotatedType()
486                         .type(type.containedType(0))
487                         .ctxAnnotations(annotatedType.getCtxAnnotations())
488                         .parent(annotatedType.getParent())
489                         .schemaProperty(annotatedType.isSchemaProperty())
490                         .name(annotatedType.getName())
491                         .resolveAsRef(annotatedType.isResolveAsRef())
492                         .jsonViewAnnotation(annotatedType.getJsonViewAnnotation())
493                         .propertyName(annotatedType.getPropertyName())
494                         .skipOverride(true);
495                 model = context.resolve(aType);
496                 return model;
497             } else {
498                 model = new Schema()
499                         .type("object")
500                         .name(name);
501             }
502         }
503
504         if (!type.isContainerType() && StringUtils.isNotBlank(name)) {
505             // define the model here to support self/cyclic referencing of models
506             context.defineModel(name, model, annotatedType, null);
507         }
508
509         XML xml = resolveXml(beanDesc.getClassInfo(), annotatedType.getCtxAnnotations(), resolvedSchemaAnnotation);
510         if (xml != null) {
511             model.xml(xml);
512         }
513
514         if (!(model instanceof ArraySchema) || (model instanceof ArraySchema && resolvedArrayAnnotation == null)) {
515             resolveSchemaMembers(model, annotatedType);
516         }
517
518         final XmlAccessorType xmlAccessorTypeAnnotation = beanDesc.getClassAnnotations().get(XmlAccessorType.class);
519
520         // see if @JsonIgnoreProperties exist
521         Set<String> propertiesToIgnore = new HashSet<String>();
522         JsonIgnoreProperties ignoreProperties = beanDesc.getClassAnnotations().get(JsonIgnoreProperties.class);
523         if (ignoreProperties != null) {
524             propertiesToIgnore.addAll(Arrays.asList(ignoreProperties.value()));
525         }
526
527         List<Schema> props = new ArrayList<Schema>();
528         Map<String, Schema> modelProps = new LinkedHashMap<String, Schema>();
529
530         List<BeanPropertyDefinition> properties = beanDesc.findProperties();
531         List<String> ignoredProps = getIgnoredProperties(beanDesc);
532         properties.removeIf(p -> ignoredProps.contains(p.getName()));
533         for (BeanPropertyDefinition propDef : properties) {
534             Schema property = null;
535             String propName = propDef.getName();
536             Annotation[] annotations = null;
537
538             AnnotatedMember member = propDef.getPrimaryMember();
539             if (member == null) {
540                 final BeanDescription deserBeanDesc = _mapper.getDeserializationConfig().introspect(type);
541                 List<BeanPropertyDefinition> deserProperties = deserBeanDesc.findProperties();
542                 for (BeanPropertyDefinition prop : deserProperties) {
543                     if (StringUtils.isNotBlank(prop.getInternalName()) && prop.getInternalName().equals(propDef.getInternalName())) {
544                         member = prop.getPrimaryMember();
545                         break;
546                     }
547                 }
548             }
549
550             // hack to avoid clobbering properties with get/is names
551             // it's ugly but gets around https://github.com/swagger-api/swagger-core/issues/415
552             if(propDef.getPrimaryMember() != null) {
553                 final JsonProperty jsonPropertyAnn = propDef.getPrimaryMember().getAnnotation(JsonProperty.class);
554                 if (jsonPropertyAnn == null || !jsonPropertyAnn.value().equals(propName)) {
555                     if (member != null) {
556                         java.lang.reflect.Member innerMember = member.getMember();
557                         if (innerMember != null) {
558                             String altName = innerMember.getName();
559                             if (altName != null) {
560                                 final int length = altName.length();
561                                 for (String prefix : Arrays.asList("get""is")) {
562                                     final int offset = prefix.length();
563                                     if (altName.startsWith(prefix) && length > offset
564                                             && !Character.isUpperCase(altName.charAt(offset))) {
565                                         propName = altName;
566                                         break;
567                                     }
568                                 }
569                             }
570                         }
571                     }
572                 }
573             }
574
575             PropertyMetadata md = propDef.getMetadata();
576
577             if (member != null && !ignore(member, xmlAccessorTypeAnnotation, propName, propertiesToIgnore)) {
578
579                 List<Annotation> annotationList = new ArrayList<Annotation>();
580                 for (Annotation a : member.annotations()) {
581                     annotationList.add(a);
582                 }
583
584                 annotations = annotationList.toArray(new Annotation[annotationList.size()]);
585
586                 if(hiddenByJsonView(annotations, annotatedType)) {
587                     continue;
588                 }
589
590                 JavaType propType = member.getType();
591                 if(propType != null && "void".equals(propType.getRawClass().getName())) {
592                     if (member instanceof AnnotatedMethod) {
593                         propType = ((AnnotatedMethod)member).getParameterType(0);
594                     }
595
596                 }
597                 String propSchemaName = null;
598                 io.swagger.v3.oas.annotations.media.Schema ctxSchema = AnnotationsUtils.getSchemaAnnotation(annotations);
599                 if (AnnotationsUtils.hasSchemaAnnotation(ctxSchema)) {
600                     if (!StringUtils.isBlank(ctxSchema.name())) {
601                         propSchemaName = ctxSchema.name();
602                     }
603                 }
604                 if (propSchemaName == null) {
605                     io.swagger.v3.oas.annotations.media.ArraySchema ctxArraySchema = AnnotationsUtils.getArraySchemaAnnotation(annotations);
606                     if (AnnotationsUtils.hasArrayAnnotation(ctxArraySchema)) {
607                         if (AnnotationsUtils.hasSchemaAnnotation(ctxArraySchema.schema())) {
608                             if (!StringUtils.isBlank(ctxArraySchema.schema().name())) {
609                                 propSchemaName = ctxArraySchema.schema().name();
610                             }
611                         }
612                     }
613                 }
614                 if (StringUtils.isNotBlank(propSchemaName)) {
615                     propName = propSchemaName;
616                 }
617                 Annotation propSchemaOrArray = AnnotationsUtils.mergeSchemaAnnotations(annotations, propType);
618                 final io.swagger.v3.oas.annotations.media.Schema propResolvedSchemaAnnotation =
619                         propSchemaOrArray == null ?
620                                 null :
621                                 propSchemaOrArray instanceof io.swagger.v3.oas.annotations.media.ArraySchema ?
622                                         ((io.swagger.v3.oas.annotations.media.ArraySchema) propSchemaOrArray).schema() :
623                                         (io.swagger.v3.oas.annotations.media.Schema) propSchemaOrArray;
624
625                 io.swagger.v3.oas.annotations.media.Schema.AccessMode accessMode = resolveAccessMode(propDef, type, propResolvedSchemaAnnotation);
626
627
628                 AnnotatedType aType = new AnnotatedType()
629                         .type(propType)
630                         .ctxAnnotations(annotations)
631                         //.name(propName)
632                         .parent(model)
633                         .resolveAsRef(annotatedType.isResolveAsRef())
634                         .jsonViewAnnotation(annotatedType.getJsonViewAnnotation())
635                         .skipSchemaName(true)
636                         .schemaProperty(true)
637                         .propertyName(propName);
638
639                 final AnnotatedMember propMember = member;
640                 aType.jsonUnwrappedHandler((t) -> {
641                     JsonUnwrapped uw = propMember.getAnnotation(JsonUnwrapped.class);
642                     if (uw != null && uw.enabled()) {
643                         t
644                             .ctxAnnotations(null)
645                             .jsonUnwrappedHandler(null)
646                             .resolveAsRef(false);
647                         handleUnwrapped(props, context.resolve(t), uw.prefix(), uw.suffix());
648                         return null;
649                     } else {
650                         return new Schema();
651                         //t.jsonUnwrappedHandler(null);
652                         //return context.resolve(t);
653                     }
654                 });
655                 property = clone(context.resolve(aType));
656
657                 if (property != null) {
658                     Boolean required = md.getRequired();
659                     if (required != null && !Boolean.FALSE.equals(required)) {
660                         addRequiredItem(model, propName);
661                     } else {
662                         if (propDef.isRequired()) {
663                             addRequiredItem(model, propName);
664                         }
665                     }
666                     if (property.get$ref() == null) {
667                         if (accessMode != null) {
668                             switch (accessMode) {
669                                 case AUTO:
670                                     break;
671                                 case READ_ONLY:
672                                     property.readOnly(true);
673                                     break;
674                                 case READ_WRITE:
675                                     break;
676                                 case WRITE_ONLY:
677                                     property.writeOnly(true);
678                                     break;
679                                 default:
680                             }
681                         }
682                     }
683                     final BeanDescription propBeanDesc = _mapper.getSerializationConfig().introspect(propType);
684                     if (property != null && !propType.isContainerType()) {
685                         if ("object".equals(property.getType())) {
686                             // create a reference for the property
687                             String pName = _typeName(propType, propBeanDesc);
688                             if (StringUtils.isNotBlank(property.getName())) {
689                                 pName = property.getName();
690                             }
691
692                             if (context.getDefinedModels().containsKey(pName)) {
693                                 property = new Schema().$ref(constructRef(pName));
694                             }
695                         } else if (property.get$ref() != null) {
696                             property = new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName());
697                         }
698                     }
699                     property.setName(propName);
700                     JAXBAnnotationsHelper.apply(propBeanDesc.getClassInfo(), annotations, property);
701                     applyBeanValidatorAnnotations(property, annotations, model);
702
703                     props.add(property);
704                 }
705             }
706         }
707         for (Schema prop : props) {
708             modelProps.put(prop.getName(), prop);
709         }
710         if (modelProps.size() > 0) {
711             model.setProperties(modelProps);
712         }
713
714         /**
715          * --Preventing parent/child hierarchy creation loops - Comment 2--
716          * Creating a parent model will result in the creation of child models, as per the first If statement following
717          * this comment. Creating a child model will result in the creation of a parent model, as per the second If
718          * statement following this comment.
719          *
720          * The current model must be defined in the context immediately. This done to help prevent repeated
721          * loops where  parents create children and children create parents when a hierarchy is present. This logic
722          * works in conjunction with the "early checking" performed earlier in this method
723          * (See "Preventing parent/child hierarchy creation loops - Comment 1"), to prevent repeated creation loops.
724          *
725          *
726          * As an aside, defining the current model in the context immediately also ensures that child models are
727          * available for modification by resolveSubtypes, when their parents are created.
728          */

729         if (!type.isContainerType() && StringUtils.isNotBlank(name)) {
730             context.defineModel(name, model, annotatedType, null);
731         }
732
733         /**
734          * This must be done after model.setProperties so that the model's set
735          * of properties is available to filter from any subtypes
736          **/

737         if (!resolveSubtypes(model, beanDesc, context)) {
738             model.setDiscriminator(null);
739         }
740
741         Discriminator discriminator = resolveDiscriminator(type, context);
742         if (discriminator != null) {
743             model.setDiscriminator(discriminator);
744         }
745
746         if (resolvedSchemaAnnotation != null) {
747             String ref = resolvedSchemaAnnotation.ref();
748             // consider ref as is
749             if (!StringUtils.isBlank(ref)) {
750                 model.$ref(ref);
751             }
752             Class<?> not = resolvedSchemaAnnotation.not();
753             if (!Void.class.equals(not)) {
754                 model.not((new Schema().$ref(context.resolve(new AnnotatedType().type(not).jsonViewAnnotation(annotatedType.getJsonViewAnnotation())).getName())));
755             }
756             if (resolvedSchemaAnnotation.requiredProperties() != null &&
757                     resolvedSchemaAnnotation.requiredProperties().length > 0 &&
758                     StringUtils.isNotBlank(resolvedSchemaAnnotation.requiredProperties()[0])) {
759                 for (String prop : resolvedSchemaAnnotation.requiredProperties()) {
760                     addRequiredItem(model, prop);
761                 }
762             }
763         }
764
765         if (isComposedSchema) {
766
767             ComposedSchema composedSchema = (ComposedSchema) model;
768
769             Class<?>[] allOf = resolvedSchemaAnnotation.allOf();
770             Class<?>[] anyOf = resolvedSchemaAnnotation.anyOf();
771             Class<?>[] oneOf = resolvedSchemaAnnotation.oneOf();
772
773             List<Class<?>> allOfFiltered = Stream.of(allOf)
774                     .distinct()
775                     .filter(c -> !this.shouldIgnoreClass(c))
776                     .filter(c -> !(c.equals(Void.class)))
777                     .collect(Collectors.toList());
778             allOfFiltered.forEach(c -> {
779                 Schema allOfRef = context.resolve(new AnnotatedType().type(c).jsonViewAnnotation(annotatedType.getJsonViewAnnotation()));
780                 Schema refSchema = new Schema().$ref(allOfRef.getName());
781                 // allOf could have already being added during subtype resolving
782                 if (composedSchema.getAllOf() == null || !composedSchema.getAllOf().contains(refSchema)) {
783                     composedSchema.addAllOfItem(refSchema);
784                 }
785                 // remove shared properties defined in the parent
786                 if (isSubtype(beanDesc.getClassInfo(), c)) {
787                     removeParentProperties(composedSchema, allOfRef);
788                 }
789             });
790
791             List<Class<?>> anyOfFiltered = Stream.of(anyOf)
792                     .distinct()
793                     .filter(c -> !this.shouldIgnoreClass(c))
794                     .filter(c -> !(c.equals(Void.class)))
795                     .collect(Collectors.toList());
796             anyOfFiltered.forEach(c -> {
797                 Schema anyOfRef = context.resolve(new AnnotatedType().type(c).jsonViewAnnotation(annotatedType.getJsonViewAnnotation()));
798                 composedSchema.addAnyOfItem(new Schema().$ref(anyOfRef.getName()));
799                 // remove shared properties defined in the parent
800                 if (isSubtype(beanDesc.getClassInfo(), c)) {
801                     removeParentProperties(composedSchema, anyOfRef);
802                 }
803
804             });
805
806             List<Class<?>> oneOfFiltered = Stream.of(oneOf)
807                     .distinct()
808                     .filter(c -> !this.shouldIgnoreClass(c))
809                     .filter(c -> !(c.equals(Void.class)))
810                     .collect(Collectors.toList());
811             oneOfFiltered.forEach(c -> {
812                 Schema oneOfRef = context.resolve(new AnnotatedType().type(c).jsonViewAnnotation(annotatedType.getJsonViewAnnotation()));
813                 if (oneOfRef != null) {
814                     if (StringUtils.isBlank(oneOfRef.getName())) {
815                         composedSchema.addOneOfItem(oneOfRef);
816                     } else {
817                         composedSchema.addOneOfItem(new Schema().$ref(oneOfRef.getName()));
818                     }
819                     // remove shared properties defined in the parent
820                     if (isSubtype(beanDesc.getClassInfo(), c)) {
821                         removeParentProperties(composedSchema, oneOfRef);
822                     }
823                 }
824
825             });
826
827             if (!composedModelPropertiesAsSibling) {
828                 if (composedSchema.getAllOf() != null && !composedSchema.getAllOf().isEmpty()) {
829                     if (composedSchema.getProperties() != null && !composedSchema.getProperties().isEmpty()) {
830                         ObjectSchema propSchema = new ObjectSchema();
831                         propSchema.properties(composedSchema.getProperties());
832                         composedSchema.setProperties(null);
833                         composedSchema.addAllOfItem(propSchema);
834                     }
835                 }
836             }
837         }
838
839         if (!type.isContainerType() && StringUtils.isNotBlank(name)) {
840             // define the model here to support self/cyclic referencing of models
841             context.defineModel(name, model, annotatedType, null);
842         }
843
844         if (model != null && annotatedType.isResolveAsRef() &&
845             (isComposedSchema || "object".equals(model.getType())) &&
846             StringUtils.isNotBlank(model.getName()))
847         {
848             if (context.getDefinedModels().containsKey(model.getName())) {
849                 model = new Schema().$ref(constructRef(model.getName()));
850             }
851         } else if (model != null && model.get$ref() != null) {
852             model = new Schema().$ref(StringUtils.isNotEmpty(model.get$ref()) ? model.get$ref() : model.getName());
853         }
854
855         if (model != null && resolvedArrayAnnotation != null) {
856             if (!"array".equals(model.getType())) {
857                 ArraySchema schema = new ArraySchema();
858                 schema.setItems(model);
859                 resolveArraySchema(annotatedType, schema, resolvedArrayAnnotation);
860                 return schema;
861             } else {
862                 if (model instanceof ArraySchema) {
863                     resolveArraySchema(annotatedType, (ArraySchema) model, resolvedArrayAnnotation);
864                 }
865             }
866         }
867
868         resolveDiscriminatorProperty(type, context, model);
869
870         return model;
871     }
872
873     private boolean shouldResolveEnumAsRef(io.swagger.v3.oas.annotations.media.Schema resolvedSchemaAnnotation) {
874         return (resolvedSchemaAnnotation != null && resolvedSchemaAnnotation.enumAsRef()) || ModelResolver.enumsAsRef;
875     }
876
877     protected Type findJsonValueType(final BeanDescription beanDesc) {
878
879         // use recursion to check for method findJsonValueAccessor existence (Jackson 2.9+)
880         // if not found use previous deprecated method which could lead to inaccurate result
881         try {
882             Method m = BeanDescription.class.getMethod("findJsonValueAccessor"null);
883             AnnotatedMember jsonValueMember = (AnnotatedMember)m.invoke(beanDesc, null);
884             if (jsonValueMember != null) {
885                 return jsonValueMember.getType();
886             }
887         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
888             LOGGER.warn("jackson BeanDescription.findJsonValueAccessor not found, this could lead to inaccurate result, please update jackson to 2.9+");
889             final AnnotatedMethod jsonValueMethod  = beanDesc.findJsonValueMethod();
890             if (jsonValueMethod != null) {
891                 return jsonValueMethod.getType();
892             }
893         }
894         return null;
895     }
896
897     private Schema clone(Schema property) {
898         if(property == null)
899             return property;
900         try {
901             String cloneName = property.getName();
902             property = Json.mapper().readValue(Json.pretty(property), Schema.class);
903             property.setName(cloneName);
904         } catch (IOException e) {
905             LOGGER.error("Could not clone property, e");
906         }
907         return property;
908     }
909
910     private boolean isSubtype(AnnotatedClass childClass, Class<?> parentClass) {
911         final BeanDescription parentDesc = _mapper.getSerializationConfig().introspectClassAnnotations(parentClass);
912         List<NamedType> subTypes =_intr.findSubtypes(parentDesc.getClassInfo());
913         if (subTypes == null) {
914             return false;
915         }
916         for (NamedType subtype : subTypes) {
917             final Class<?> subtypeType = subtype.getType();
918             if (childClass.getRawType().isAssignableFrom(subtypeType)) {
919                 return true;
920             }
921         }
922         return false;
923     }
924
925
926     protected boolean _isOptionalType(JavaType propType) {
927         return Arrays.asList("com.google.common.base.Optional""java.util.Optional")
928                 .contains(propType.getRawClass().getCanonicalName());
929     }
930
931     protected void _addEnumProps(Class<?> propClass, Schema property) {
932         final boolean useIndex = _mapper.isEnabled(SerializationFeature.WRITE_ENUMS_USING_INDEX);
933         final boolean useToString = _mapper.isEnabled(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
934
935         @SuppressWarnings("unchecked")
936         Class<Enum<?>> enumClass = (Class<Enum<?>>) propClass;
937         for (Enum<?> en : enumClass.getEnumConstants()) {
938             String n;
939             if (useIndex) {
940                 n = String.valueOf(en.ordinal());
941             } else if (useToString) {
942                 n = en.toString();
943             } else {
944                 n = _intr.findEnumValue(en);
945             }
946             if (property instanceof StringSchema) {
947                 StringSchema sp = (StringSchema) property;
948                 sp.addEnumItem(n);
949             }
950         }
951     }
952
953     protected boolean ignore(final Annotated member, final XmlAccessorType xmlAccessorTypeAnnotation, final String propName, final Set<String> propertiesToIgnore) {
954         if (propertiesToIgnore.contains(propName)) {
955             return true;
956         }
957         if (member.hasAnnotation(JsonIgnore.class)) {
958             return true;
959         }
960         if (xmlAccessorTypeAnnotation == null) {
961             return false;
962         }
963         if (xmlAccessorTypeAnnotation.value().equals(XmlAccessType.NONE)) {
964             if (!member.hasAnnotation(XmlElement.class) &&
965                     !member.hasAnnotation(XmlAttribute.class) &&
966                     !member.hasAnnotation(XmlElementRef.class) &&
967                     !member.hasAnnotation(XmlElementRefs.class) &&
968                     !member.hasAnnotation(JsonProperty.class)) {
969                 return true;
970             }
971         }
972         return false;
973     }
974
975     private void handleUnwrapped(List<Schema> props, Schema innerModel, String prefix, String suffix) {
976         if (StringUtils.isBlank(suffix) && StringUtils.isBlank(prefix)) {
977             if (innerModel.getProperties() != null) {
978                 props.addAll(innerModel.getProperties().values());
979             }
980         } else {
981             if (prefix == null) {
982                 prefix = "";
983             }
984             if (suffix == null) {
985                 suffix = "";
986             }
987             if (innerModel.getProperties() != null) {
988                 for (Schema prop : (Collection<Schema>) innerModel.getProperties().values()) {
989                     try {
990                         Schema clonedProp = Json.mapper().readValue(Json.pretty(prop), Schema.class);
991                         clonedProp.setName(prefix + prop.getName() + suffix);
992                         props.add(clonedProp);
993                     } catch (IOException e) {
994                         LOGGER.error("Exception cloning property", e);
995                         return;
996                     }
997                 }
998             }
999         }
1000     }
1001
1002     private enum GeneratorWrapper {
1003         PROPERTY(ObjectIdGenerators.PropertyGenerator.class) {
1004             @Override
1005             protected Schema processAsProperty(String propertyName, AnnotatedType type,
1006                                                ModelConverterContext context, ObjectMapper mapper) {
1007                 /*
1008                  * When generator = ObjectIdGenerators.PropertyGenerator.class and
1009                  * @JsonIdentityReference(alwaysAsId = false) then property is serialized
1010                  * in the same way it is done without @JsonIdentityInfo annotation.
1011                  */

1012                 return null;
1013             }
1014
1015             @Override
1016             protected Schema processAsId(String propertyName, AnnotatedType type,
1017                                          ModelConverterContext context, ObjectMapper mapper) {
1018                 final JavaType javaType;
1019                 if (type.getType() instanceof JavaType) {
1020                     javaType = (JavaType)type.getType();
1021                 } else {
1022                     javaType = mapper.constructType(type.getType());
1023                 }
1024                 final BeanDescription beanDesc = mapper.getSerializationConfig().introspect(javaType);
1025                 for (BeanPropertyDefinition def : beanDesc.findProperties()) {
1026                     final String name = def.getName();
1027                     if (name != null && name.equals(propertyName)) {
1028                         final AnnotatedMember propMember = def.getPrimaryMember();
1029                         final JavaType propType = propMember.getType();
1030                         if (PrimitiveType.fromType(propType) != null) {
1031                             return PrimitiveType.createProperty(propType);
1032                         } else {
1033                             List<Annotation> list = new ArrayList<>();
1034                             for (Annotation a : propMember.annotations()) {
1035                                 list.add(a);
1036                             }
1037                             Annotation[] annotations = list.toArray(new Annotation[list.size()]);
1038                             Annotation propSchemaOrArray = AnnotationsUtils.mergeSchemaAnnotations(annotations, propType);
1039                             AnnotatedType aType = new AnnotatedType()
1040                                     .type(propType)
1041                                     .ctxAnnotations(annotations)
1042                                     .jsonViewAnnotation(type.getJsonViewAnnotation())
1043                                     .schemaProperty(true)
1044                                     .propertyName(type.getPropertyName());
1045
1046                             return context.resolve(aType);
1047                         }
1048                     }
1049                 }
1050                 return null;
1051             }
1052         },
1053         INT(ObjectIdGenerators.IntSequenceGenerator.class) {
1054             @Override
1055             protected Schema processAsProperty(String propertyName, AnnotatedType type,
1056                                                ModelConverterContext context, ObjectMapper mapper) {
1057                 Schema id = new IntegerSchema();
1058                 return process(id, propertyName, type, context);
1059             }
1060
1061             @Override
1062             protected Schema processAsId(String propertyName, AnnotatedType type,
1063                                          ModelConverterContext context, ObjectMapper mapper) {
1064                 return new IntegerSchema();
1065             }
1066         },
1067         UUID(ObjectIdGenerators.UUIDGenerator.class) {
1068             @Override
1069             protected Schema processAsProperty(String propertyName, AnnotatedType type,
1070                                                ModelConverterContext context, ObjectMapper mapper) {
1071                 Schema id = new UUIDSchema();
1072                 return process(id, propertyName, type, context);
1073             }
1074
1075             @Override
1076             protected Schema processAsId(String propertyName, AnnotatedType type,
1077                                          ModelConverterContext context, ObjectMapper mapper) {
1078                 return new UUIDSchema();
1079             }
1080         },
1081         NONE(ObjectIdGenerators.None.class) {
1082             // When generator = ObjectIdGenerators.None.class property should be processed as normal property.
1083             @Override
1084             protected Schema processAsProperty(String propertyName, AnnotatedType type,
1085                                                ModelConverterContext context, ObjectMapper mapper) {
1086                 return null;
1087             }
1088
1089             @Override
1090             protected Schema processAsId(String propertyName, AnnotatedType type,
1091                                          ModelConverterContext context, ObjectMapper mapper) {
1092                 return null;
1093             }
1094         };
1095
1096         private final Class<? extends ObjectIdGenerator> generator;
1097
1098         GeneratorWrapper(Class<? extends ObjectIdGenerator> generator) {
1099             this.generator = generator;
1100         }
1101
1102         protected abstract Schema processAsProperty(String propertyName, AnnotatedType type,
1103                                                     ModelConverterContext context, ObjectMapper mapper);
1104
1105         protected abstract Schema processAsId(String propertyName, AnnotatedType type,
1106                                               ModelConverterContext context, ObjectMapper mapper);
1107
1108         public static Schema processJsonIdentity(AnnotatedType type, ModelConverterContext context,
1109                                                  ObjectMapper mapper, JsonIdentityInfo identityInfo,
1110                                                  JsonIdentityReference identityReference) {
1111             final GeneratorWrapper wrapper = identityInfo != null ? getWrapper(identityInfo.generator()) : null;
1112             if (wrapper == null) {
1113                 return null;
1114             }
1115             if (identityReference != null && identityReference.alwaysAsId()) {
1116                 return wrapper.processAsId(identityInfo.property(), type, context, mapper);
1117             } else {
1118                 return wrapper.processAsProperty(identityInfo.property(), type, context, mapper);
1119             }
1120         }
1121
1122         private static GeneratorWrapper getWrapper(Class<?> generator) {
1123             for (GeneratorWrapper value : GeneratorWrapper.values()) {
1124                 if (value.generator.isAssignableFrom(generator)) {
1125                     return value;
1126                 }
1127             }
1128             return null;
1129         }
1130
1131         private static Schema process(Schema id, String propertyName, AnnotatedType type,
1132                                       ModelConverterContext context) {
1133
1134             Schema model = context.resolve(removeJsonIdentityAnnotations(type));
1135             Schema mi = model;
1136             mi.addProperties(propertyName, id);
1137             return new Schema().$ref(StringUtils.isNotEmpty(mi.get$ref())
1138                     ? mi.get$ref() : mi.getName());
1139         }
1140         private static AnnotatedType removeJsonIdentityAnnotations(AnnotatedType type) {
1141             return new AnnotatedType()
1142                     .jsonUnwrappedHandler(type.getJsonUnwrappedHandler())
1143                     .jsonViewAnnotation(type.getJsonViewAnnotation())
1144                     .name(type.getName())
1145                     .parent(type.getParent())
1146                     .resolveAsRef(false)
1147                     .schemaProperty(type.isSchemaProperty())
1148                     .skipOverride(type.isSkipOverride())
1149                     .skipSchemaName(type.isSkipSchemaName())
1150                     .type(type.getType())
1151                     .skipJsonIdentity(true)
1152                     .propertyName(type.getPropertyName())
1153                     .ctxAnnotations(AnnotationsUtils.removeAnnotations(type.getCtxAnnotations(), JsonIdentityInfo.class, JsonIdentityReference.class));
1154         }
1155     }
1156
1157     protected void applyBeanValidatorAnnotations(Schema property, Annotation[] annotations, Schema parent) {
1158         Map<String, Annotation> annos = new HashMap<String, Annotation>();
1159         if (annotations != null) {
1160             for (Annotation anno : annotations) {
1161                 annos.put(anno.annotationType().getName(), anno);
1162             }
1163         }
1164         if (parent != null &&
1165                 (
1166                         annos.containsKey("javax.validation.constraints.NotNull") ||
1167                         annos.containsKey("javax.validation.constraints.NotBlank") ||
1168                         annos.containsKey("javax.validation.constraints.NotEmpty")
1169                 )) {
1170             addRequiredItem(parent, property.getName());
1171         }
1172         if (annos.containsKey("javax.validation.constraints.Min")) {
1173             if ("integer".equals(property.getType()) || "number".equals(property.getType())) {
1174                 Min min = (Min) annos.get("javax.validation.constraints.Min");
1175                 property.setMinimum(new BigDecimal(min.value()));
1176             }
1177         }
1178         if (annos.containsKey("javax.validation.constraints.Max")) {
1179             if ("integer".equals(property.getType()) || "number".equals(property.getType())) {
1180                 Max max = (Max) annos.get("javax.validation.constraints.Max");
1181                 property.setMaximum(new BigDecimal(max.value()));
1182             }
1183         }
1184         if (annos.containsKey("javax.validation.constraints.Size")) {
1185             Size size = (Size) annos.get("javax.validation.constraints.Size");
1186             if ("integer".equals(property.getType()) || "number".equals(property.getType())) {
1187                 property.setMinimum(new BigDecimal(size.min()));
1188                 property.setMaximum(new BigDecimal(size.max()));
1189             } else if (property instanceof StringSchema) {
1190                 StringSchema sp = (StringSchema) property;
1191                 sp.minLength(new Integer(size.min()));
1192                 sp.maxLength(new Integer(size.max()));
1193             } else if (property instanceof ArraySchema) {
1194                 ArraySchema sp = (ArraySchema) property;
1195                 sp.setMinItems(size.min());
1196                 sp.setMaxItems(size.max());
1197             }
1198         }
1199         if (annos.containsKey("javax.validation.constraints.DecimalMin")) {
1200             DecimalMin min = (DecimalMin) annos.get("javax.validation.constraints.DecimalMin");
1201             if (property instanceof NumberSchema) {
1202                 NumberSchema ap = (NumberSchema) property;
1203                 ap.setMinimum(new BigDecimal(min.value()));
1204                 ap.setExclusiveMinimum(!min.inclusive());
1205             }
1206         }
1207         if (annos.containsKey("javax.validation.constraints.DecimalMax")) {
1208             DecimalMax max = (DecimalMax) annos.get("javax.validation.constraints.DecimalMax");
1209             if (property instanceof NumberSchema) {
1210                 NumberSchema ap = (NumberSchema) property;
1211                 ap.setMaximum(new BigDecimal(max.value()));
1212                 ap.setExclusiveMaximum(!max.inclusive());
1213             }
1214         }
1215         if (annos.containsKey("javax.validation.constraints.Pattern")) {
1216             Pattern pattern = (Pattern) annos.get("javax.validation.constraints.Pattern");
1217             if (property instanceof StringSchema) {
1218                 property.setPattern(pattern.regexp());
1219             }
1220         }
1221     }
1222
1223     private boolean resolveSubtypes(Schema model, BeanDescription bean, ModelConverterContext context) {
1224         final List<NamedType> types = _intr.findSubtypes(bean.getClassInfo());
1225         if (types == null) {
1226             return false;
1227         }
1228
1229         /**
1230          * Remove the current class from the child classes. This happens if @JsonSubTypes references
1231          * the annotated class as a subtype.
1232          */

1233         removeSelfFromSubTypes(types, bean);
1234
1235         /**
1236          * As the introspector will find @JsonSubTypes for a child class that are present on its super classes, the
1237          * code segment below will also run the introspector on the parent class, and then remove any sub-types that are
1238          * found for the parent from the sub-types found for the child. The same logic all applies to implemented
1239          * interfaces, and is accounted for below.
1240          */

1241         removeSuperClassAndInterfaceSubTypes(types, bean);
1242
1243         int count = 0;
1244         final Class<?> beanClass = bean.getClassInfo().getAnnotated();
1245         for (NamedType subtype : types) {
1246             final Class<?> subtypeType = subtype.getType();
1247             if (!beanClass.isAssignableFrom(subtypeType)) {
1248                 continue;
1249             }
1250
1251             final Schema subtypeModel = context.resolve(new AnnotatedType().type(subtypeType));
1252
1253             if (    StringUtils.isBlank(subtypeModel.getName()) ||
1254                     subtypeModel.getName().equals(model.getName())) {
1255                 subtypeModel.setName(_typeNameResolver.nameForType(_mapper.constructType(subtypeType),
1256                         TypeNameResolver.Options.SKIP_API_MODEL));
1257             }
1258
1259             // here schema could be not composed, but we want it to be composed, doing same work as done
1260             // in resolve method??
1261
1262             ComposedSchema composedSchema = null;
1263             if (!(subtypeModel instanceof ComposedSchema)) {
1264                 // create composed schema
1265                 // TODO #2312 - smarter way needs clone implemented in #2227
1266                 composedSchema = (ComposedSchema) new ComposedSchema()
1267                         .title(subtypeModel.getTitle())
1268                         .name(subtypeModel.getName())
1269                         .deprecated(subtypeModel.getDeprecated())
1270                         .additionalProperties(subtypeModel.getAdditionalProperties())
1271                         .description(subtypeModel.getDescription())
1272                         .discriminator(subtypeModel.getDiscriminator())
1273                         .example(subtypeModel.getExample())
1274                         .exclusiveMaximum(subtypeModel.getExclusiveMaximum())
1275                         .exclusiveMinimum(subtypeModel.getExclusiveMinimum())
1276                         .externalDocs(subtypeModel.getExternalDocs())
1277                         .format(subtypeModel.getFormat())
1278                         .maximum(subtypeModel.getMaximum())
1279                         .maxItems(subtypeModel.getMaxItems())
1280                         .maxLength(subtypeModel.getMaxLength())
1281                         .maxProperties(subtypeModel.getMaxProperties())
1282                         .minimum(subtypeModel.getMinimum())
1283                         .minItems(subtypeModel.getMinItems())
1284                         .minLength(subtypeModel.getMinLength())
1285                         .minProperties(subtypeModel.getMinProperties())
1286                         .multipleOf(subtypeModel.getMultipleOf())
1287                         .not(subtypeModel.getNot())
1288                         .nullable(subtypeModel.getNullable())
1289                         .pattern(subtypeModel.getPattern())
1290                         .properties(subtypeModel.getProperties())
1291                         .readOnly(subtypeModel.getReadOnly())
1292                         .required(subtypeModel.getRequired())
1293                         .type(subtypeModel.getType())
1294                         .uniqueItems(subtypeModel.getUniqueItems())
1295                         .writeOnly(subtypeModel.getWriteOnly())
1296                         .xml(subtypeModel.getXml())
1297                         .extensions(subtypeModel.getExtensions());
1298
1299                 composedSchema.setEnum(subtypeModel.getEnum());
1300             } else {
1301                 composedSchema = (ComposedSchema) subtypeModel;
1302             }
1303             Schema refSchema = new Schema().$ref(model.getName());
1304             // allOf could have already being added during type resolving when @Schema(allOf..) is declared
1305             if (composedSchema.getAllOf() == null || !composedSchema.getAllOf().contains(refSchema)) {
1306                 composedSchema.addAllOfItem(refSchema);
1307             }
1308             removeParentProperties(composedSchema, model);
1309             if (!composedModelPropertiesAsSibling) {
1310                 if (composedSchema.getAllOf() != null && !composedSchema.getAllOf().isEmpty()) {
1311                     if (composedSchema.getProperties() != null && !composedSchema.getProperties().isEmpty()) {
1312                         ObjectSchema propSchema = new ObjectSchema();
1313                         propSchema.properties(composedSchema.getProperties());
1314                         composedSchema.setProperties(null);
1315                         composedSchema.addAllOfItem(propSchema);
1316                     }
1317                 }
1318             }
1319
1320
1321             // replace previous schema..
1322             Class<?> currentType = subtype.getType();
1323             if (StringUtils.isNotBlank(composedSchema.getName())) {
1324                 context.defineModel(composedSchema.getName(), composedSchema, new AnnotatedType().type(currentType), null);
1325             }
1326
1327
1328         }
1329         return count != 0;
1330     }
1331
1332     private void removeSelfFromSubTypes(List<NamedType> types, BeanDescription bean) {
1333         Class<?> beanClass= bean.getType().getRawClass();
1334         types.removeIf(type -> beanClass.equals(type.getType()));
1335     }
1336
1337     private void removeSuperClassAndInterfaceSubTypes(List<NamedType> types, BeanDescription bean) {
1338         Class<?> beanClass = bean.getType().getRawClass();
1339         Class<?> superClass = beanClass.getSuperclass();
1340         if (superClass != null && !superClass.equals(Object.class)) {
1341             removeSuperSubTypes(types, superClass);
1342         }
1343         if (!types.isEmpty()) {
1344             Class<?>[] superInterfaces = beanClass.getInterfaces();
1345             for (Class<?> superInterface : superInterfaces) {
1346                 removeSuperSubTypes(types, superInterface);
1347                 if (types.isEmpty()) {
1348                     break;
1349                 }
1350             }
1351         }
1352     }
1353
1354     private void removeSuperSubTypes(List<NamedType> resultTypes, Class<?> superClass) {
1355         JavaType superType = _mapper.constructType(superClass);
1356         BeanDescription superBean = _mapper.getSerializationConfig().introspect(superType);
1357         final List<NamedType> superTypes = _intr.findSubtypes(superBean.getClassInfo());
1358         if (superTypes != null) {
1359             resultTypes.removeAll(superTypes);
1360         }
1361     }
1362
1363     private void removeParentProperties(Schema child, Schema parent) {
1364         final Map<String, Schema> baseProps = parent.getProperties();
1365         final Map<String, Schema> subtypeProps = child.getProperties();
1366         if (baseProps != null && subtypeProps != null) {
1367             for (Map.Entry<String, Schema> entry : baseProps.entrySet()) {
1368                 if (entry.getValue().equals(subtypeProps.get(entry.getKey()))) {
1369                     subtypeProps.remove(entry.getKey());
1370                 }
1371             }
1372         }
1373         if (subtypeProps == null || subtypeProps.isEmpty()) {
1374             child.setProperties(null);
1375         }
1376     }
1377
1378     protected List<Class<?>> getComposedSchemaReferencedClasses(Class<?> clazz, Annotation[] ctxAnnotations, io.swagger.v3.oas.annotations.media.Schema schemaAnnotation) {
1379
1380         if (schemaAnnotation != null) {
1381             Class<?>[] allOf = schemaAnnotation.allOf();
1382             Class<?>[] anyOf = schemaAnnotation.anyOf();
1383             Class<?>[] oneOf = schemaAnnotation.oneOf();
1384
1385             // try to read all of them anyway and resolve?
1386             List<Class<?>> parentClasses = Stream.of(allOf, anyOf, oneOf)
1387                     .flatMap(Stream::of)
1388                     .distinct()
1389                     .filter(c -> !this.shouldIgnoreClass(c))
1390                     .filter(c -> !(c.equals(Void.class)))
1391                     .collect(Collectors.toList());
1392
1393             if (!parentClasses.isEmpty()) {
1394                 return parentClasses;
1395             }
1396         }
1397         return null;
1398     }
1399
1400     protected String resolveDescription(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1401         if (schema != null && !"".equals(schema.description())) {
1402             return schema.description();
1403         }
1404         return null;
1405     }
1406
1407     protected String resolveTitle(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1408         if (schema != null && StringUtils.isNotBlank(schema.title())) {
1409             return schema.title();
1410         }
1411         return null;
1412     }
1413
1414     protected String resolveFormat(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1415         if (schema != null && StringUtils.isNotBlank(schema.format())) {
1416             return schema.format();
1417         }
1418         return null;
1419     }
1420
1421     protected String resolveDefaultValue(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1422         if (schema != null) {
1423             if (!schema.defaultValue().isEmpty()) {
1424                 return schema.defaultValue();
1425             }
1426         }
1427         if (a == null) {
1428             return null;
1429         }
1430         XmlElement elem = a.getAnnotation(XmlElement.class);
1431         if (elem == null) {
1432             if (annotations != null) {
1433                 for (Annotation ann: annotations) {
1434                     if (ann instanceof XmlElement) {
1435                         elem = (XmlElement)ann;
1436                         break;
1437                     }
1438                 }
1439             }
1440         }
1441         if (elem != null) {
1442             if (!elem.defaultValue().isEmpty() && !"\u0000".equals(elem.defaultValue())) {
1443                 return elem.defaultValue();
1444             }
1445         }
1446         return null;
1447     }
1448
1449     protected Object resolveExample(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1450
1451         if (schema != null) {
1452             if (!schema.example().isEmpty()) {
1453                 try {
1454                     ObjectMapper mapper = ObjectMapperFactory.buildStrictGenericObjectMapper();
1455                     return mapper.readTree(schema.example());
1456                 } catch (IOException e) {
1457                     return schema.example();
1458                 }
1459             }
1460         }
1461
1462         return null;
1463     }
1464
1465     protected io.swagger.v3.oas.annotations.media.Schema.AccessMode resolveAccessMode(BeanPropertyDefinition propDef, JavaType type, io.swagger.v3.oas.annotations.media.Schema schema) {
1466         if (schema != null && !schema.accessMode().equals(io.swagger.v3.oas.annotations.media.Schema.AccessMode.AUTO)) {
1467             return schema.accessMode();
1468         } else if (schema != null && schema.readOnly()) {
1469             return io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
1470         } else if (schema != null && schema.writeOnly()) {
1471             return io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY;
1472         }
1473
1474         if (propDef == null) {
1475             return null;
1476         }
1477         JsonProperty.Access access = null;
1478         if (propDef instanceof POJOPropertyBuilder) {
1479             access = ((POJOPropertyBuilder) propDef).findAccess();
1480         }
1481         boolean hasGetter = propDef.hasGetter();
1482         boolean hasSetter = propDef.hasSetter();
1483         boolean hasConstructorParameter = propDef.hasConstructorParameter();
1484         boolean hasField = propDef.hasField();
1485
1486
1487         if (access == null) {
1488             final BeanDescription beanDesc = _mapper.getDeserializationConfig().introspect(type);
1489             List<BeanPropertyDefinition> properties = beanDesc.findProperties();
1490             for (BeanPropertyDefinition prop : properties) {
1491                 if (StringUtils.isNotBlank(prop.getInternalName()) && prop.getInternalName().equals(propDef.getInternalName())) {
1492                     if (prop instanceof POJOPropertyBuilder) {
1493                         access = ((POJOPropertyBuilder) prop).findAccess();
1494                     }
1495                     hasGetter = hasGetter || prop.hasGetter();
1496                     hasSetter = hasSetter || prop.hasSetter();
1497                     hasConstructorParameter = hasConstructorParameter || prop.hasConstructorParameter();
1498                     hasField = hasField || prop.hasField();
1499                     break;
1500                 }
1501             }
1502         }
1503         if (access == null) {
1504             if (!hasGetter && !hasField && (hasConstructorParameter || hasSetter)) {
1505                 return io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY;
1506             }
1507             return null;
1508         } else {
1509             switch (access) {
1510                 case AUTO:
1511                     return io.swagger.v3.oas.annotations.media.Schema.AccessMode.AUTO;
1512                 case READ_ONLY:
1513                     return io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY;
1514                 case READ_WRITE:
1515                     return io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE;
1516                 case WRITE_ONLY:
1517                     return io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY;
1518                 default:
1519                     return io.swagger.v3.oas.annotations.media.Schema.AccessMode.AUTO;
1520             }
1521         }
1522     }
1523
1524     protected Boolean resolveReadOnly(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1525         if (schema != null && schema.accessMode().equals(io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY)) {
1526             return true;
1527         } else if (schema != null && schema.accessMode().equals(io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY)) {
1528             return null;
1529         } else if (schema != null && schema.accessMode().equals(io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE)) {
1530             return null;
1531         } else if (schema != null && schema.readOnly()) {
1532             return schema.readOnly();
1533         }
1534         return null;
1535     }
1536
1537
1538     protected Boolean resolveNullable(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1539         if (schema != null && schema.nullable()) {
1540             return schema.nullable();
1541         }
1542         return null;
1543     }
1544
1545     protected BigDecimal resolveMultipleOf(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1546         if (schema != null && schema.multipleOf() != 0) {
1547             return new BigDecimal(schema.multipleOf());
1548         }
1549         return null;
1550     }
1551
1552     protected Integer resolveMaxLength(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1553         if (schema != null && schema.maxLength() != Integer.MAX_VALUE && schema.maxLength() > 0) {
1554             return schema.maxLength();
1555         }
1556         return null;
1557     }
1558
1559     protected Integer resolveMinLength(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1560         if (schema != null && schema.minLength() > 0) {
1561             return schema.minLength();
1562         }
1563         return null;
1564     }
1565
1566     protected BigDecimal resolveMinimum(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1567         if (schema != null && NumberUtils.isNumber(schema.minimum())) {
1568             String filteredMinimum = schema.minimum().replaceAll(Constants.COMMA, StringUtils.EMPTY);
1569             return new BigDecimal(filteredMinimum);
1570         }
1571         return null;
1572     }
1573
1574     protected BigDecimal resolveMaximum(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1575         if (schema != null && NumberUtils.isNumber(schema.maximum())) {
1576             String filteredMaximum = schema.maximum().replaceAll(Constants.COMMA, StringUtils.EMPTY);
1577             return new BigDecimal(filteredMaximum);
1578         }
1579         return null;
1580     }
1581
1582     protected Boolean resolveExclusiveMinimum(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1583         if (schema != null && schema.exclusiveMinimum()) {
1584             return schema.exclusiveMinimum();
1585         }
1586         return null;
1587     }
1588
1589     protected Boolean resolveExclusiveMaximum(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1590         if (schema != null && schema.exclusiveMaximum()) {
1591             return schema.exclusiveMaximum();
1592         }
1593         return null;
1594     }
1595
1596     protected String resolvePattern(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1597         if (schema != null && StringUtils.isNotBlank(schema.pattern())) {
1598             return schema.pattern();
1599         }
1600         return null;
1601     }
1602
1603     protected Integer resolveMinProperties(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1604         if (schema != null && schema.minProperties() > 0) {
1605             return schema.minProperties();
1606         }
1607         return null;
1608     }
1609
1610     protected Integer resolveMaxProperties(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1611         if (schema != null && schema.maxProperties() > 0) {
1612             return schema.maxProperties();
1613         }
1614         return null;
1615     }
1616
1617     protected List<String> resolveRequiredProperties(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1618         if (schema != null &&
1619                 schema.requiredProperties() != null &&
1620                 schema.requiredProperties().length > 0 &&
1621                 StringUtils.isNotBlank(schema.requiredProperties()[0])) {
1622
1623             return Arrays.asList(schema.requiredProperties());
1624         }
1625         return null;
1626     }
1627
1628     protected Boolean resolveWriteOnly(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1629         if (schema != null && schema.accessMode().equals(io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_ONLY)) {
1630             return null;
1631         } else if (schema != null && schema.accessMode().equals(io.swagger.v3.oas.annotations.media.Schema.AccessMode.WRITE_ONLY)) {
1632             return true;
1633         } else if (schema != null && schema.accessMode().equals(io.swagger.v3.oas.annotations.media.Schema.AccessMode.READ_WRITE)) {
1634             return null;
1635         } else if (schema != null && schema.writeOnly()) {
1636             return schema.writeOnly();
1637         }
1638         return null;
1639     }
1640
1641     protected ExternalDocumentation resolveExternalDocumentation(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1642
1643         ExternalDocumentation external = null;
1644         if (a != null) {
1645             io.swagger.v3.oas.annotations.ExternalDocumentation externalDocumentation = a.getAnnotation(io.swagger.v3.oas.annotations.ExternalDocumentation.class);
1646             external = resolveExternalDocumentation(externalDocumentation);
1647         }
1648
1649         if (external == null) {
1650             if (schema != null) {
1651                 external = resolveExternalDocumentation(schema.externalDocs());
1652             }
1653         }
1654         return external;
1655     }
1656
1657     protected ExternalDocumentation resolveExternalDocumentation(io.swagger.v3.oas.annotations.ExternalDocumentation externalDocumentation) {
1658
1659         if (externalDocumentation == null) {
1660             return null;
1661         }
1662         boolean isEmpty = true;
1663         ExternalDocumentation external = new ExternalDocumentation();
1664         if (StringUtils.isNotBlank(externalDocumentation.description())) {
1665             isEmpty = false;
1666             external.setDescription(externalDocumentation.description());
1667         }
1668         if (StringUtils.isNotBlank(externalDocumentation.url())) {
1669             isEmpty = false;
1670             external.setUrl(externalDocumentation.url());
1671         }
1672         if (isEmpty) {
1673             return null;
1674         }
1675         return external;
1676     }
1677
1678     protected Boolean resolveDeprecated(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1679         if (schema != null && schema.deprecated()) {
1680             return schema.deprecated();
1681         }
1682         return null;
1683     }
1684
1685     protected List<String> resolveAllowableValues(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1686         if (schema != null &&
1687                 schema.allowableValues() != null &&
1688                 schema.allowableValues().length > 0) {
1689             return Arrays.asList(schema.allowableValues());
1690         }
1691         return null;
1692     }
1693
1694     protected Map<String, Object> resolveExtensions(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1695         if (schema != null &&
1696                 schema.extensions() != null &&
1697                 schema.extensions().length > 0) {
1698             return AnnotationsUtils.getExtensions(schema.extensions());
1699         }
1700         return null;
1701     }
1702
1703     protected void resolveDiscriminatorProperty(JavaType type, ModelConverterContext context, Schema model) {
1704         // add JsonTypeInfo.property if not member of bean
1705         JsonTypeInfo typeInfo = type.getRawClass().getDeclaredAnnotation(JsonTypeInfo.class);
1706         if (typeInfo != null) {
1707             String typeInfoProp = typeInfo.property();
1708             if (StringUtils.isNotBlank(typeInfoProp)) {
1709                 Schema modelToUpdate = model;
1710                 if (StringUtils.isNotBlank(model.get$ref())) {
1711                     modelToUpdate = context.getDefinedModels().get(model.get$ref().substring(21));
1712                 }
1713                 if (modelToUpdate.getProperties() == null || !modelToUpdate.getProperties().keySet().contains(typeInfoProp)) {
1714                     Schema discriminatorSchema = new StringSchema().name(typeInfoProp);
1715                     modelToUpdate.addProperties(typeInfoProp, discriminatorSchema);
1716                     if (modelToUpdate.getRequired() == null || !modelToUpdate.getRequired().contains(typeInfoProp)) {
1717                         modelToUpdate.addRequiredItem(typeInfoProp);
1718                     }
1719                 }
1720             }
1721         }
1722     }
1723
1724     protected Discriminator resolveDiscriminator(JavaType type, ModelConverterContext context) {
1725
1726         io.swagger.v3.oas.annotations.media.Schema declaredSchemaAnnotation = AnnotationsUtils.getSchemaDeclaredAnnotation(type.getRawClass());
1727
1728         String disc = (declaredSchemaAnnotation == null) ? "" : declaredSchemaAnnotation.discriminatorProperty();
1729
1730         if (disc.isEmpty()) {
1731             // longer method would involve AnnotationIntrospector.findTypeResolver(...) but:
1732             JsonTypeInfo typeInfo = type.getRawClass().getDeclaredAnnotation(JsonTypeInfo.class);
1733             if (typeInfo != null) {
1734                 disc = typeInfo.property();
1735             }
1736         }
1737         if (!disc.isEmpty()) {
1738             Discriminator discriminator = new Discriminator()
1739                     .propertyName(disc);
1740             if (declaredSchemaAnnotation != null) {
1741                 DiscriminatorMapping mappings[] = declaredSchemaAnnotation.discriminatorMapping();
1742                 if (mappings != null && mappings.length > 0) {
1743                     for (DiscriminatorMapping mapping : mappings) {
1744                         if (!mapping.value().isEmpty() && !mapping.schema().equals(Void.class)) {
1745                             discriminator.mapping(mapping.value(), constructRef(context.resolve(new AnnotatedType().type(mapping.schema())).getName()));
1746                         }
1747                     }
1748                 }
1749             }
1750
1751             return discriminator;
1752         }
1753         return null;
1754     }
1755
1756     protected XML resolveXml(Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schema) {
1757         // if XmlRootElement annotation, construct an Xml object and attach it to the model
1758         XmlRootElement rootAnnotation = null;
1759         if (a != null) {
1760             rootAnnotation = a.getAnnotation(XmlRootElement.class);
1761         }
1762         if (rootAnnotation == null) {
1763             if (annotations != null) {
1764                 for (Annotation ann: annotations) {
1765                     if (ann instanceof XmlRootElement) {
1766                         rootAnnotation = (XmlRootElement)ann;
1767                         break;
1768                     }
1769                 }
1770             }
1771         }
1772         if (rootAnnotation != null && !"".equals(rootAnnotation.name()) && !"##default".equals(rootAnnotation.name())) {
1773             XML xml = new XML().name(rootAnnotation.name());
1774             if (rootAnnotation.namespace() != null && !"".equals(rootAnnotation.namespace()) && !"##default".equals(rootAnnotation.namespace())) {
1775                 xml.namespace(rootAnnotation.namespace());
1776             }
1777             return xml;
1778         }
1779         return null;
1780     }
1781
1782     protected Integer resolveMinItems(AnnotatedType a, io.swagger.v3.oas.annotations.media.ArraySchema arraySchema) {
1783         if (arraySchema != null) {
1784             if (arraySchema.minItems() < Integer.MAX_VALUE) {
1785                 return arraySchema.minItems();
1786             }
1787         }
1788         return null;
1789     }
1790
1791     protected Integer resolveMaxItems(AnnotatedType a, io.swagger.v3.oas.annotations.media.ArraySchema arraySchema) {
1792         if (arraySchema != null) {
1793             if (arraySchema.maxItems() > 0) {
1794                 return arraySchema.maxItems();
1795             }
1796         }
1797         return null;
1798     }
1799
1800     protected Boolean resolveUniqueItems(AnnotatedType a, io.swagger.v3.oas.annotations.media.ArraySchema arraySchema) {
1801         if (arraySchema != null) {
1802             if (arraySchema.uniqueItems()) {
1803                 return arraySchema.uniqueItems();
1804             }
1805         }
1806         return null;
1807     }
1808
1809     protected Map<String, Object> resolveExtensions(AnnotatedType a, io.swagger.v3.oas.annotations.media.ArraySchema arraySchema) {
1810         if (arraySchema != null &&
1811                 arraySchema.extensions() != null &&
1812                 arraySchema.extensions().length > 0) {
1813             return AnnotationsUtils.getExtensions(arraySchema.extensions());
1814         }
1815         return null;
1816     }
1817
1818
1819     protected void resolveSchemaMembers(Schema schema, AnnotatedType annotatedType) {
1820         final JavaType type;
1821         if (annotatedType.getType() instanceof JavaType) {
1822             type = (JavaType) annotatedType.getType();
1823         } else {
1824             type = _mapper.constructType(annotatedType.getType());
1825         }
1826
1827         final Annotation resolvedSchemaOrArrayAnnotation = AnnotationsUtils.mergeSchemaAnnotations(annotatedType.getCtxAnnotations(), type);
1828         final io.swagger.v3.oas.annotations.media.Schema schemaAnnotation =
1829                 resolvedSchemaOrArrayAnnotation == null ?
1830                         null :
1831                         resolvedSchemaOrArrayAnnotation instanceof io.swagger.v3.oas.annotations.media.ArraySchema ?
1832                                 ((io.swagger.v3.oas.annotations.media.ArraySchema) resolvedSchemaOrArrayAnnotation).schema() :
1833                                 (io.swagger.v3.oas.annotations.media.Schema) resolvedSchemaOrArrayAnnotation;
1834
1835         final BeanDescription beanDesc = _mapper.getSerializationConfig().introspect(type);
1836         Annotated a = beanDesc.getClassInfo();
1837         Annotation[] annotations = annotatedType.getCtxAnnotations();
1838         resolveSchemaMembers(schema, a, annotations, schemaAnnotation);
1839     }
1840
1841     protected void resolveSchemaMembers(Schema schema, Annotated a, Annotation[] annotations, io.swagger.v3.oas.annotations.media.Schema schemaAnnotation) {
1842
1843         String description = resolveDescription(a, annotations, schemaAnnotation);
1844         if (StringUtils.isNotBlank(description)) {
1845             schema.description(description);
1846         }
1847         String title = resolveTitle(a, annotations, schemaAnnotation);
1848         if (StringUtils.isNotBlank(title)) {
1849             schema.title(title);
1850         }
1851         String format = resolveFormat(a, annotations, schemaAnnotation);
1852         if (StringUtils.isNotBlank(format) && StringUtils.isBlank(schema.getFormat())) {
1853             schema.format(format);
1854         }
1855         String defaultValue = resolveDefaultValue(a, annotations, schemaAnnotation);
1856         if (StringUtils.isNotBlank(defaultValue)) {
1857             schema.setDefault(defaultValue);
1858         }
1859         Object example = resolveExample(a, annotations, schemaAnnotation);
1860         if (example != null) {
1861             schema.example(example);
1862         }
1863         Boolean readOnly = resolveReadOnly(a, annotations, schemaAnnotation);
1864         if (readOnly != null) {
1865             schema.readOnly(readOnly);
1866         }
1867         Boolean nullable = resolveNullable(a, annotations, schemaAnnotation);
1868         if (nullable != null) {
1869             schema.nullable(nullable);
1870         }
1871         BigDecimal multipleOf = resolveMultipleOf(a, annotations, schemaAnnotation);
1872         if (multipleOf != null) {
1873             schema.multipleOf(multipleOf);
1874         }
1875         Integer maxLength = resolveMaxLength(a, annotations, schemaAnnotation);
1876         if (maxLength != null) {
1877             schema.maxLength(maxLength);
1878         }
1879         Integer minLength = resolveMinLength(a, annotations, schemaAnnotation);
1880         if (minLength != null) {
1881             schema.minLength(minLength);
1882         }
1883         BigDecimal minimum = resolveMinimum(a, annotations, schemaAnnotation);
1884         if (minimum != null) {
1885             schema.minimum(minimum);
1886         }
1887         BigDecimal maximum = resolveMaximum(a, annotations, schemaAnnotation);
1888         if (maximum != null) {
1889             schema.maximum(maximum);
1890         }
1891         Boolean exclusiveMinimum = resolveExclusiveMinimum(a, annotations, schemaAnnotation);
1892         if (exclusiveMinimum != null) {
1893             schema.exclusiveMinimum(exclusiveMinimum);
1894         }
1895         Boolean exclusiveMaximum = resolveExclusiveMaximum(a, annotations, schemaAnnotation);
1896         if (exclusiveMaximum != null) {
1897             schema.exclusiveMaximum(exclusiveMaximum);
1898         }
1899         String pattern = resolvePattern(a, annotations, schemaAnnotation);
1900         if (StringUtils.isNotBlank(pattern)) {
1901             schema.pattern(pattern);
1902         }
1903         Integer minProperties = resolveMinProperties(a, annotations, schemaAnnotation);
1904         if (minProperties != null) {
1905             schema.minProperties(minProperties);
1906         }
1907         Integer maxProperties = resolveMaxProperties(a, annotations, schemaAnnotation);
1908         if (maxProperties != null) {
1909             schema.maxProperties(maxProperties);
1910         }
1911         List<String> requiredProperties = resolveRequiredProperties(a, annotations, schemaAnnotation);
1912         if (requiredProperties != null) {
1913             for (String prop : requiredProperties) {
1914                 addRequiredItem(schema, prop);
1915             }
1916         }
1917         Boolean writeOnly = resolveWriteOnly(a, annotations, schemaAnnotation);
1918         if (writeOnly != null) {
1919             schema.writeOnly(writeOnly);
1920         }
1921         ExternalDocumentation externalDocs = resolveExternalDocumentation(a, annotations, schemaAnnotation);
1922         if (externalDocs != null) {
1923             schema.externalDocs(externalDocs);
1924         }
1925         Boolean deprecated = resolveDeprecated(a, annotations, schemaAnnotation);
1926         if (deprecated != null) {
1927             schema.deprecated(deprecated);
1928         }
1929         List<String> allowableValues = resolveAllowableValues(a, annotations, schemaAnnotation);
1930         if (allowableValues != null) {
1931             for (String prop : allowableValues) {
1932                 schema.addEnumItemObject(prop);
1933             }
1934         }
1935
1936         Map<String, Object> extensions = resolveExtensions(a, annotations, schemaAnnotation);
1937         if (extensions != null) {
1938             for (String ext : extensions.keySet()) {
1939                 schema.addExtension(ext, extensions.get(ext));
1940             }
1941         }
1942     }
1943
1944     protected void addRequiredItem(Schema model, String propName) {
1945         if (model == null || propName == null || StringUtils.isBlank(propName)) {
1946             return;
1947         }
1948         if (model.getRequired() == null || model.getRequired().isEmpty()) {
1949             model.addRequiredItem(propName);
1950         }
1951         if (model.getRequired().stream().noneMatch(s -> propName.equals(s))) {
1952             model.addRequiredItem(propName);
1953         }
1954     }
1955
1956     protected boolean shouldIgnoreClass(Type type) {
1957         if (type instanceof Class) {
1958             Class<?> cls = (Class<?>) type;
1959             if (cls.getName().equals("javax.ws.rs.Response")) {
1960                 return true;
1961             }
1962         } else {
1963             if (type instanceof com.fasterxml.jackson.core.type.ResolvedType) {
1964                 com.fasterxml.jackson.core.type.ResolvedType rt = (com.fasterxml.jackson.core.type.ResolvedType) type;
1965                 LOGGER.trace("Can't check class {}, {}", type, rt.getRawClass().getName());
1966                 if (rt.getRawClass().equals(Class.class)) {
1967                     return true;
1968                 }
1969             }
1970         }
1971         return false;
1972     }
1973
1974     private List<String> getIgnoredProperties(BeanDescription beanDescription) {
1975         AnnotationIntrospector introspector = _mapper.getSerializationConfig().getAnnotationIntrospector();
1976         String[] ignored = introspector.findPropertiesToIgnore(beanDescription.getClassInfo(), true);
1977         return ignored == null ? Collections.emptyList() : Arrays.asList(ignored);
1978     }
1979
1980     /**
1981      *  Decorate the name based on the JsonView
1982      */

1983     protected String decorateModelName(AnnotatedType type, String originalName) {
1984         if (StringUtils.isBlank(originalName)) {
1985             return originalName;
1986         }
1987         String name = originalName;
1988         if (type.getJsonViewAnnotation() != null && type.getJsonViewAnnotation().value().length > 0) {
1989             String COMBINER = "-or-";
1990             StringBuilder sb = new StringBuilder();
1991             for (Class<?> view : type.getJsonViewAnnotation().value()) {
1992                 sb.append(view.getSimpleName()).append(COMBINER);
1993             }
1994             String suffix = sb.substring(0, sb.length() - COMBINER.length());
1995             name = originalName + "_" + suffix;
1996         }
1997         return name;
1998     }
1999
2000     protected boolean hiddenByJsonView(Annotation[] annotations,
2001                                      AnnotatedType type) {
2002         JsonView jsonView = type.getJsonViewAnnotation();
2003         if (jsonView == null)
2004             return false;
2005
2006         Class<?>[] filters = jsonView.value();
2007         boolean containsJsonViewAnnotation = false;
2008         for (Annotation ant : annotations) {
2009             if (ant instanceof JsonView) {
2010                 containsJsonViewAnnotation = true;
2011                 Class<?>[] views = ((JsonView) ant).value();
2012                 for (Class<?> f : filters) {
2013                     for (Class<?> v : views) {
2014                         if (v == f || v.isAssignableFrom(f)) {
2015                             return false;
2016                         }
2017                     }
2018                 }
2019             }
2020         }
2021         return containsJsonViewAnnotation;
2022     }
2023
2024     private void resolveArraySchema(AnnotatedType annotatedType, ArraySchema schema, io.swagger.v3.oas.annotations.media.ArraySchema resolvedArrayAnnotation) {
2025         Integer minItems = resolveMinItems(annotatedType, resolvedArrayAnnotation);
2026         if (minItems != null) {
2027             schema.minItems(minItems);
2028         }
2029         Integer maxItems = resolveMaxItems(annotatedType, resolvedArrayAnnotation);
2030         if (maxItems != null) {
2031             schema.maxItems(maxItems);
2032         }
2033         Boolean uniqueItems = resolveUniqueItems(annotatedType, resolvedArrayAnnotation);
2034         if (uniqueItems != null) {
2035             schema.uniqueItems(uniqueItems);
2036         }
2037         Map<String, Object> extensions = resolveExtensions(annotatedType, resolvedArrayAnnotation);
2038         if (extensions != null) {
2039             schema.extensions(extensions);
2040         }
2041         if (resolvedArrayAnnotation != null) {
2042             if (AnnotationsUtils.hasSchemaAnnotation(resolvedArrayAnnotation.arraySchema())) {
2043                 resolveSchemaMembers(schema, nullnull, resolvedArrayAnnotation.arraySchema());
2044             }
2045         }
2046     }
2047 }
2048