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
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
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
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
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
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
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
333 context.defineModel(name, model, annotatedType, null);
334
335 model = new Schema().$ref(name);
336 }
337 return model;
338 }
339
340
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
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
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
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
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
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
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
551
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
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
652
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
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
729 if (!type.isContainerType() && StringUtils.isNotBlank(name)) {
730 context.defineModel(name, model, annotatedType, null);
731 }
732
733
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
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
782 if (composedSchema.getAllOf() == null || !composedSchema.getAllOf().contains(refSchema)) {
783 composedSchema.addAllOfItem(refSchema);
784 }
785
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
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
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
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
880
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
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
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
1233 removeSelfFromSubTypes(types, bean);
1234
1235
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
1260
1261
1262 ComposedSchema composedSchema = null;
1263 if (!(subtypeModel instanceof ComposedSchema)) {
1264
1265
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
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
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
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
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
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
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
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, null, null, resolvedArrayAnnotation.arraySchema());
2044 }
2045 }
2046 }
2047 }
2048