ModelConverters.java

package io.swagger.v3.core.converter;

import com.fasterxml.jackson.databind.type.TypeFactory;
import io.swagger.v3.core.jackson.ModelResolver;
import io.swagger.v3.core.util.Configuration;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.Json31;
import io.swagger.v3.oas.models.media.Schema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

public class ModelConverters {
    private static ModelConverters SINGLETON = null;
    private static ModelConverters SINGLETON31 = null;
    static Logger LOGGER = LoggerFactory.getLogger(ModelConverters.class);
    private final List<ModelConverter> converters;
    private final Set<String> skippedPackages = new HashSet<>();
    private final Set<String> skippedClasses = new HashSet<>();

    public ModelConverters() {
        converters = new CopyOnWriteArrayList<>();
        converters.add(new ModelResolver(Json.mapper()));
    }

    public ModelConverters(boolean openapi31) {
        converters = new CopyOnWriteArrayList<>();
        if (openapi31) {
            converters.add(new ModelResolver(Json31.mapper()).openapi31(true));
        } else {
            converters.add(new ModelResolver(Json.mapper()));
        }
    }

    public ModelConverters(boolean openapi31, Schema.SchemaResolution schemaResolution) {
        converters = new CopyOnWriteArrayList<>();
        if (openapi31) {
            converters.add(new ModelResolver(Json31.mapper()).openapi31(true).schemaResolution(schemaResolution));
        } else {
            converters.add(new ModelResolver(Json.mapper()).schemaResolution(schemaResolution));
        }
    }

    public ModelConverters(Configuration configuration) {
        converters = new CopyOnWriteArrayList<>();
        boolean openapi31 =configuration != null && configuration.isOpenAPI31() != null && configuration.isOpenAPI31();
        if (openapi31) {
            converters.add(new ModelResolver(Json31.mapper()).configuration(configuration));
        } else {
            converters.add(new ModelResolver(Json.mapper()).configuration(configuration));
        }
    }

    public Set<String> getSkippedPackages() {
        return skippedPackages;
    }

    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;
    }

    public static void reset() {
        synchronized (ModelConverters.class) {
            SINGLETON = null;
            SINGLETON31 = null;
        }
    }

    public static ModelConverters getInstance(boolean openapi31, Schema.SchemaResolution schemaResolution) {
        synchronized (ModelConverters.class) {
            if (openapi31) {
                if (SINGLETON31 == null) {
                    boolean applySchemaResolution = Boolean.parseBoolean(System.getProperty(Schema.APPLY_SCHEMA_RESOLUTION_PROPERTY, "false")) || Boolean.parseBoolean(System.getenv(Schema.APPLY_SCHEMA_RESOLUTION_PROPERTY));
                    SINGLETON31 = new ModelConverters(openapi31, applySchemaResolution ? schemaResolution : Schema.SchemaResolution.DEFAULT);
                    init(SINGLETON31);
                }
                return SINGLETON31;
            }
            if (SINGLETON == null) {
                SINGLETON = new ModelConverters(openapi31, schemaResolution);
                init(SINGLETON);
            }
            return SINGLETON;
        }
    }

    public static ModelConverters getInstance(Configuration configuration) {
        synchronized (ModelConverters.class) {
            boolean openapi31 = configuration != null && configuration.isOpenAPI31() != null && configuration.isOpenAPI31();
            if (openapi31) {
                if (SINGLETON31 == null) {
                    boolean applySchemaResolution = Boolean.parseBoolean(System.getProperty(Schema.APPLY_SCHEMA_RESOLUTION_PROPERTY, "false")) || Boolean.parseBoolean(System.getenv(Schema.APPLY_SCHEMA_RESOLUTION_PROPERTY));
                    if (!applySchemaResolution) {
                        configuration.schemaResolution(Schema.SchemaResolution.DEFAULT);
                    }
                    SINGLETON31 = new ModelConverters(configuration);
                    init(SINGLETON31);
                }
                return SINGLETON31;
            }
            if (SINGLETON == null) {
                SINGLETON = new ModelConverters(configuration);
                init(SINGLETON);
            }
            return SINGLETON;
        }
    }

    private static void init(ModelConverters converter) {
        converter.addPackageToSkip("java.lang");
        converter.addPackageToSkip("groovy.lang");

        ServiceLoader<ModelConverter> loader = ServiceLoader.load(ModelConverter.class);
        Iterator<ModelConverter> 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);
    }

    public void removeConverter(ModelConverter converter) {
        converters.remove(converter);
    }

    public List<ModelConverter> getConverters() {
        return Collections.unmodifiableList(converters);
    }

    public void addPackageToSkip(String pkg) {
        this.skippedPackages.add(pkg);
    }

    public void addClassToSkip(String cls) {
        LOGGER.warn("skipping class {}", cls);
        this.skippedClasses.add(cls);
    }

    public Map<String, Schema> read(Type type) {
        return read(new AnnotatedType().type(type));
    }

    public Map<String, Schema> read(AnnotatedType type) {
        Map<String, Schema> modelMap = new HashMap<>();
        if (shouldProcess(type.getType())) {
            ModelConverterContextImpl context = new ModelConverterContextImpl(
                    converters);
            Schema resolve = context.resolve(type);
            for (Entry<String, Schema> entry : context.getDefinedModels()
                    .entrySet()) {
                if (entry.getValue().equals(resolve)) {
                    modelMap.put(entry.getKey(), entry.getValue());
                }
            }
        }
        return modelMap;
    }

    public Map<String, Schema> readAll(Type type) {
        return readAll(new AnnotatedType().type(type));
    }

    public Map<String, Schema> readAll(AnnotatedType type) {
        if (shouldProcess(type.getType())) {
            ModelConverterContextImpl context = new ModelConverterContextImpl(
                    converters);

            LOGGER.debug("ModelConverters readAll from {}", type);
            context.resolve(type);
            return context.getDefinedModels();
        }
        return new HashMap<>();
    }

    public ResolvedSchema readAllAsResolvedSchema(Type type) {
        return readAllAsResolvedSchema(new AnnotatedType().type(type));
    }
    public ResolvedSchema readAllAsResolvedSchema(AnnotatedType type) {
        if (shouldProcess(type.getType())) {
            return resolveAsResolvedSchema(type);
        }
        return null;
    }

    public ResolvedSchema resolveAsResolvedSchema(AnnotatedType type) {
        ModelConverterContextImpl context = new ModelConverterContextImpl(
                converters);

        ResolvedSchema resolvedSchema = new ResolvedSchema();
        resolvedSchema.schema = context.resolve(type);
        resolvedSchema.referencedSchemas = context.getDefinedModels();

        return resolvedSchema;
    }

    public boolean isRegisteredAsSkippedClass(String className) {
        return skippedClasses.contains(className);
    }

    private boolean shouldProcess(Type type) {
        final Class<?> cls = TypeFactory.defaultInstance().constructType(type).getRawClass();
        if (cls.isPrimitive()) {
            return false;
        }
        String className = cls.getName();
        for (String packageName : skippedPackages) {
            if (className.startsWith(packageName)) {
                return false;
            }
        }
        return !skippedClasses.contains(className);
    }
}