diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java index 67ab75d647..5f6008648a 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/converter/ModelConverters.java @@ -21,7 +21,8 @@ import java.util.concurrent.CopyOnWriteArrayList; public class ModelConverters { - private static final ModelConverters SINGLETON = new ModelConverters(); + private static ModelConverters SINGLETON = null; + private static ModelConverters SINGLETON31 = null; static Logger LOGGER = LoggerFactory.getLogger(ModelConverters.class); private final List converters; private final Set skippedPackages = new HashSet<>(); @@ -41,10 +42,42 @@ public ModelConverters(boolean openapi31) { } } - public static ModelConverters getInstance() { + public static ModelConverters getInstance(boolean openapi31) { + if (openapi31) { + if (SINGLETON31 == null) { + SINGLETON31 = new ModelConverters(openapi31); + init(SINGLETON31); + } + return SINGLETON31; + } + if (SINGLETON == null) { + SINGLETON = new ModelConverters(openapi31); + init(SINGLETON); + } return SINGLETON; } + private static void init(ModelConverters converter) { + converter.skippedPackages.add("java.lang"); + + ServiceLoader loader = ServiceLoader.load(ModelConverter.class); + Iterator itr = loader.iterator(); + while (itr.hasNext()) { + ModelConverter ext = itr.next(); + if (ext == null) { + LOGGER.error("failed to load extension {}", ext); + } else { + converter.addConverter(ext); + LOGGER.debug("adding ModelConverter: {}", ext); + } + } + + } + public static ModelConverters getInstance() { + return getInstance(false); + } + + public void addConverter(ModelConverter converter) { converters.add(0, converter); } @@ -140,20 +173,4 @@ private boolean shouldProcess(Type type) { } return !skippedClasses.contains(className); } - - static { - SINGLETON.skippedPackages.add("java.lang"); - - ServiceLoader loader = ServiceLoader.load(ModelConverter.class); - Iterator itr = loader.iterator(); - while (itr.hasNext()) { - ModelConverter ext = itr.next(); - if (ext == null) { - LOGGER.error("failed to load extension {}", ext); - } else { - SINGLETON.addConverter(ext); - LOGGER.debug("adding ModelConverter: {}", ext); - } - } - } } diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java index 354a42e91e..40055b07b5 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/jackson/ModelResolver.java @@ -55,6 +55,7 @@ import io.swagger.v3.oas.models.media.ComposedSchema; import io.swagger.v3.oas.models.media.Discriminator; import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.JsonSchema; import io.swagger.v3.oas.models.media.MapSchema; import io.swagger.v3.oas.models.media.NumberSchema; import io.swagger.v3.oas.models.media.ObjectSchema; @@ -205,11 +206,15 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context name = decorateModelName(annotatedType, name); - // if we have a ref we don't consider anything else + // if we have a ref, for OAS 3.0 we don't consider anything else, while for OAS 3.1 we store the ref and add it later + String schemaRefFromAnnotation = null; if (resolvedSchemaAnnotation != null && StringUtils.isNotEmpty(resolvedSchemaAnnotation.ref())) { if (resolvedArrayAnnotation == null) { - return new Schema().$ref(resolvedSchemaAnnotation.ref()).name(name); + schemaRefFromAnnotation = resolvedSchemaAnnotation.ref(); + if (!openapi31) { + return new JsonSchema().$ref(resolvedSchemaAnnotation.ref()).name(name); + } } else { ArraySchema schema = new ArraySchema(); resolveArraySchema(annotatedType, schema, resolvedArrayAnnotation); @@ -336,7 +341,11 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context } if ("Object".equals(name)) { - return new Schema(); + Schema schema = new Schema(); + if (schemaRefFromAnnotation != null) { + schema.raw$ref(schemaRefFromAnnotation); + } + return schema; } List> composedSchemaReferencedClasses = getComposedSchemaReferencedClasses(type.getRawClass(), annotatedType.getCtxAnnotations(), resolvedSchemaAnnotation); @@ -362,6 +371,9 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context model = new Schema().$ref(Components.COMPONENTS_SCHEMAS_REF + name); } if (!isComposedSchema) { + if (schemaRefFromAnnotation != null && model != null) { + model.raw$ref(schemaRefFromAnnotation); + } return model; } } @@ -712,7 +724,13 @@ public Schema resolve(AnnotatedType annotatedType, ModelConverterContext context property = new Schema().$ref(constructRef(pName)); } } else if (property.get$ref() != null) { - property = new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName()); + if (!openapi31) { + property = new Schema().$ref(StringUtils.isNotEmpty(property.get$ref()) ? property.get$ref() : property.getName()); + } else { + if (StringUtils.isEmpty(property.get$ref())) { + property.$ref(property.getName()); + } + } } } property.setName(propName); diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java index 133ad88a6d..2eb0e753e2 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/AnnotationsUtils.java @@ -795,12 +795,7 @@ public static Schema resolveSchemaFromType(Class schemaImplementation, Compon schemaObject = primitiveType.createProperty(); } else { schemaObject = new Schema(); - ResolvedSchema resolvedSchema; - if (openapi31) { - resolvedSchema = new ModelConverters(true).readAllAsResolvedSchema(new AnnotatedType().type(schemaImplementation).jsonViewAnnotation(jsonViewAnnotation)); - } else { - resolvedSchema = ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(schemaImplementation).jsonViewAnnotation(jsonViewAnnotation)); - } + ResolvedSchema resolvedSchema = ModelConverters.getInstance(openapi31).readAllAsResolvedSchema(new AnnotatedType().type(schemaImplementation).jsonViewAnnotation(jsonViewAnnotation)); Map schemaMap; if (resolvedSchema != null) { schemaMap = resolvedSchema.referencedSchemas; diff --git a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java index 007d99c0a7..de969ff72c 100644 --- a/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java +++ b/modules/swagger-core/src/main/java/io/swagger/v3/core/util/ParameterProcessor.java @@ -66,12 +66,7 @@ public static Parameter applyAnnotations( .jsonViewAnnotation(jsonViewAnnotation) .ctxAnnotations(reworkedAnnotations.toArray(new Annotation[reworkedAnnotations.size()])); - final ResolvedSchema resolvedSchema; - if (openapi31) { - resolvedSchema = new ModelConverters(true).resolveAsResolvedSchema(annotatedType); - } else { - resolvedSchema = ModelConverters.getInstance().resolveAsResolvedSchema(annotatedType); - } + final ResolvedSchema resolvedSchema = ModelConverters.getInstance(openapi31).resolveAsResolvedSchema(annotatedType); if (resolvedSchema.schema != null) { parameter.setSchema(resolvedSchema.schema); diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java index d16ef22849..1784e04cbc 100644 --- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java +++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/GenericOpenApiContext.java @@ -524,7 +524,7 @@ public T init() throws OpenApiConfigurationException { if (objectMapperProcessor != null) { ObjectMapper mapper = IntegrationObjectMapperFactory.createJson(); objectMapperProcessor.processJsonObjectMapper(mapper); - ModelConverters.getInstance().addConverter(new ModelResolver(mapper)); + ModelConverters.getInstance(Boolean.TRUE.equals(openApiConfiguration.isOpenAPI31())).addConverter(new ModelResolver(mapper)); objectMapperProcessor.processOutputJsonObjectMapper(outputJsonMapper); objectMapperProcessor.processOutputYamlObjectMapper(outputYamlMapper); @@ -537,7 +537,7 @@ public T init() throws OpenApiConfigurationException { try { if (modelConverters != null && !modelConverters.isEmpty()) { for (ModelConverter converter: modelConverters) { - ModelConverters.getInstance().addConverter(converter); + ModelConverters.getInstance(Boolean.TRUE.equals(openApiConfiguration.isOpenAPI31())).addConverter(converter); } } } catch (Exception e) { diff --git a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java index c3ebd46080..0a348d1f76 100644 --- a/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java +++ b/modules/swagger-integration/src/main/java/io/swagger/v3/oas/integration/SwaggerConfiguration.java @@ -34,7 +34,7 @@ public class SwaggerConfiguration implements OpenAPIConfiguration { private Boolean alwaysResolveAppPath; - private Boolean openAPI31; + private Boolean openAPI31 = false; private Boolean convertToOpenAPI31; diff --git a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java index 9b624a7fad..fe1b713186 100644 --- a/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java +++ b/modules/swagger-jaxrs2/src/main/java/io/swagger/v3/jaxrs2/Reader.java @@ -101,6 +101,7 @@ public Reader() { paths = new Paths(); openApiTags = new LinkedHashSet<>(); components = new Components(); + setConfiguration(new SwaggerConfiguration().openAPI(openAPI)); } @@ -1120,7 +1121,7 @@ protected Operation parseMethod( final Class subResource = getSubResourceWithJaxRsSubresourceLocatorSpecs(method); Schema returnTypeSchema = null; if (!shouldIgnoreClass(returnType.getTypeName()) && !method.getGenericReturnType().equals(subResource)) { - ResolvedSchema resolvedSchema = ModelConverters.getInstance().resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation)); + ResolvedSchema resolvedSchema = ModelConverters.getInstance(config.isOpenAPI31()).resolveAsResolvedSchema(new AnnotatedType(returnType).resolveAsRef(true).jsonViewAnnotation(jsonViewAnnotation)); if (resolvedSchema.schema != null) { returnTypeSchema = resolvedSchema.schema; Content content = new Content(); @@ -1231,7 +1232,7 @@ private boolean shouldIgnoreClass(String className) { } ignore = rawClassName.startsWith("javax.ws.rs."); ignore = ignore || rawClassName.equalsIgnoreCase("void"); - ignore = ignore || ModelConverters.getInstance().isRegisteredAsSkippedClass(rawClassName); + ignore = ignore || ModelConverters.getInstance(config.isOpenAPI31()).isRegisteredAsSkippedClass(rawClassName); return ignore; } diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java index 2caca607a1..acd7b58694 100644 --- a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/ReaderTest.java @@ -12,8 +12,10 @@ import io.swagger.v3.core.jackson.ModelResolver; import io.swagger.v3.core.model.ApiDescription; import io.swagger.v3.core.util.PrimitiveType; +import io.swagger.v3.core.util.Yaml; import io.swagger.v3.jaxrs2.matchers.SerializationMatchers; import io.swagger.v3.jaxrs2.petstore31.PetResource; +import io.swagger.v3.jaxrs2.petstore31.TagResource; import io.swagger.v3.jaxrs2.resources.ResponseReturnTypeResource; import io.swagger.v3.jaxrs2.resources.SchemaPropertiesResource; import io.swagger.v3.jaxrs2.resources.SingleExampleResource; @@ -3273,6 +3275,22 @@ public void testOas31Petstore() { " description: Pet not found\n" + "components:\n" + " schemas:\n" + + " Bar:\n" + + " deprecated: true\n" + + " description: Bar\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " const: bar\n" + + " bar:\n" + + " type: integer\n" + + " format: int32\n" + + " exclusiveMaximum: 4\n" + + " foobar:\n" + + " type:\n" + + " - integer\n" + + " - string\n" + + " format: int32\n" + " Category:\n" + " properties:\n" + " id:\n" + @@ -3282,6 +3300,23 @@ public void testOas31Petstore() { " type: string\n" + " xml:\n" + " name: Category\n" + + " Foo:\n" + + " deprecated: true\n" + + " description: Foo\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " const: foo\n" + + " bar:\n" + + " type: integer\n" + + " format: int32\n" + + " exclusiveMaximum: 2\n" + + " foobar:\n" + + " type:\n" + + " - integer\n" + + " - string\n" + + " - object\n" + + " format: int32\n" + " IfSchema:\n" + " deprecated: true\n" + " description: if schema\n" + @@ -3354,8 +3389,109 @@ public void testOas31Petstore() { " format: int64\n" + " name:\n" + " type: string\n" + + " annotated:\n" + + " $ref: '#/components/schemas/Category'\n" + + " description: child description\n" + + " properties:\n" + + " bar:\n" + + " deprecated: true\n" + + " description: Bar\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " const: bar\n" + + " bar:\n" + + " type: integer\n" + + " format: int32\n" + + " exclusiveMaximum: 4\n" + + " foobar:\n" + + " type:\n" + + " - integer\n" + + " - string\n" + + " format: int32\n" + + " foo:\n" + + " deprecated: true\n" + + " description: Foo\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " const: foo\n" + + " bar:\n" + + " type: integer\n" + + " format: int32\n" + + " exclusiveMaximum: 2\n" + + " foobar:\n" + + " type:\n" + + " - integer\n" + + " - string\n" + + " - object\n" + + " format: int32\n" + " xml:\n" + " name: Tag\n"; SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); } + + @Test + public void test31RefSiblings() { + SwaggerConfiguration config = new SwaggerConfiguration().openAPI31(true).openAPI(new OpenAPI()); + Reader reader = new Reader(config); + + OpenAPI openAPI = reader.read(TagResource.class); + String yaml = "openapi: 3.1.0\n" + + "paths:\n" + + " /tag/tag:\n" + + " get:\n" + + " operationId: getTag\n" + + " responses:\n" + + " default:\n" + + " description: default response\n" + + " content:\n" + + " '*/*':\n" + + " schema:\n" + + " $ref: '#/components/schemas/SimpleTag'\n" + + "components:\n" + + " schemas:\n" + + " Foo:\n" + + " deprecated: true\n" + + " description: Foo\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " const: foo\n" + + " bar:\n" + + " type: integer\n" + + " format: int32\n" + + " exclusiveMaximum: 2\n" + + " foobar:\n" + + " type:\n" + + " - integer\n" + + " - string\n" + + " - object\n" + + " format: int32\n" + + " SimpleTag:\n" + + " properties:\n" + + " annotated:\n" + + " $ref: '#/components/schemas/SimpleCategory'\n" + + " description: child description\n" + + " properties:\n" + + " foo:\n" + + " deprecated: true\n" + + " description: Foo\n" + + " properties:\n" + + " foo:\n" + + " type: string\n" + + " const: foo\n" + + " bar:\n" + + " type: integer\n" + + " format: int32\n" + + " exclusiveMaximum: 2\n" + + " foobar:\n" + + " type:\n" + + " - integer\n" + + " - string\n" + + " - object\n" + + " format: int32\n" + + " SimpleCategory: {}\n"; + SerializationMatchers.assertEqualsToYaml31(openAPI, yaml); + } } diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/SimpleCategory.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/SimpleCategory.java new file mode 100644 index 0000000000..93331be2b1 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/SimpleCategory.java @@ -0,0 +1,2 @@ +package io.swagger.v3.jaxrs2.petstore31; +public class SimpleCategory {} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/SimpleTag.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/SimpleTag.java new file mode 100644 index 0000000000..fb0cd43f67 --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/SimpleTag.java @@ -0,0 +1,27 @@ +package io.swagger.v3.jaxrs2.petstore31; + +import io.swagger.v3.oas.annotations.StringToClassMapItem; +import io.swagger.v3.oas.annotations.media.Schema; + +public class SimpleTag { + + @Schema( + properties = { + @StringToClassMapItem(key = "foo", value = Foo.class) + }, + ref = "#/components/schemas/SimpleCategory", + description = "child description" + ) + public Object annotated; + + @Schema(description = "Foo", deprecated = true) + static class Foo { + @Schema(_const = "foo") + public String foo; + @Schema(exclusiveMaximumValue = 2) + public int bar; + + @Schema(types = {"string", "object"}) + public int foobar; + } +} diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/Tag.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/Tag.java index 38e7dc66b4..3c5232af9e 100644 --- a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/Tag.java +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/Tag.java @@ -3,6 +3,9 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import io.swagger.v3.oas.annotations.StringToClassMapItem; +import io.swagger.v3.oas.annotations.media.Schema; + @XmlRootElement(name = "Tag") public class Tag { private long id; @@ -25,4 +28,36 @@ public String getName() { public void setName(String name) { this.name = name; } + + @Schema( + properties = { + @StringToClassMapItem(key = "foo", value = Foo.class), + @StringToClassMapItem(key = "bar", value = Bar.class) + }, + ref = "#/components/schemas/Category", + description = "child description" + ) + public Object annotated; + + @Schema(description = "Foo", deprecated = true) + static class Foo { + @Schema(_const = "foo") + public String foo; + @Schema(exclusiveMaximumValue = 2) + public int bar; + + @Schema(types = {"string", "object"}) + public int foobar; + } + + @Schema(description = "Bar", deprecated = true) + static class Bar { + @Schema(_const = "bar") + public String foo; + @Schema(exclusiveMaximumValue = 4) + public int bar; + + @Schema(types = {"string", "integer"}) + public int foobar; + } } diff --git a/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/TagResource.java b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/TagResource.java new file mode 100644 index 0000000000..ef9db8d15b --- /dev/null +++ b/modules/swagger-jaxrs2/src/test/java/io/swagger/v3/jaxrs2/petstore31/TagResource.java @@ -0,0 +1,15 @@ +package io.swagger.v3.jaxrs2.petstore31; + +import io.swagger.v3.oas.annotations.parameters.RequestBody; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/tag") +public class TagResource { + @GET + @Path("/tag") + public SimpleTag getTag(@RequestBody SimpleCategory category) { + return null; + } +}