springfox.documentation.schema.property.OptimizedModelPropertiesProvider.java Source code

Java tutorial

Introduction

Here is the source code for springfox.documentation.schema.property.OptimizedModelPropertiesProvider.java

Source

/*
 *
 *  Copyright 2015-2016 the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *
 */
package springfox.documentation.schema.property;

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.fasterxml.classmate.members.ResolvedField;
import com.fasterxml.classmate.members.ResolvedMethod;
import com.fasterxml.classmate.members.ResolvedParameterizedMember;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ModelPropertyBuilder;
import springfox.documentation.schema.ModelProperty;
import springfox.documentation.schema.TypeNameExtractor;
import springfox.documentation.schema.configuration.ObjectMapperConfigured;
import springfox.documentation.schema.plugins.SchemaPluginsManager;
import springfox.documentation.schema.property.bean.AccessorsProvider;
import springfox.documentation.schema.property.bean.BeanModelProperty;
import springfox.documentation.schema.property.bean.ParameterModelProperty;
import springfox.documentation.schema.property.field.FieldModelProperty;
import springfox.documentation.schema.property.field.FieldProvider;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import static com.google.common.collect.FluentIterable.*;
import static com.google.common.collect.Iterables.*;
import static com.google.common.collect.Lists.*;
import static com.google.common.collect.Maps.*;
import static springfox.documentation.schema.ResolvedTypes.*;
import static springfox.documentation.schema.property.BeanPropertyDefinitions.*;
import static springfox.documentation.schema.property.FactoryMethodProvider.*;
import static springfox.documentation.schema.property.bean.BeanModelProperty.*;
import static springfox.documentation.spi.schema.contexts.ModelContext.*;

@Primary
@Component("optimized")
public class OptimizedModelPropertiesProvider implements ModelPropertiesProvider {
    private static final Logger LOG = LoggerFactory.getLogger(OptimizedModelPropertiesProvider.class);
    private final AccessorsProvider accessors;
    private final FieldProvider fields;
    private final FactoryMethodProvider factoryMethods;
    private final TypeResolver typeResolver;
    private final BeanPropertyNamingStrategy namingStrategy;
    private final SchemaPluginsManager schemaPluginsManager;
    private final TypeNameExtractor typeNameExtractor;
    private ObjectMapper objectMapper;

    @Autowired
    public OptimizedModelPropertiesProvider(AccessorsProvider accessors, FieldProvider fields,
            FactoryMethodProvider factoryMethods, TypeResolver typeResolver,
            BeanPropertyNamingStrategy namingStrategy, SchemaPluginsManager schemaPluginsManager,
            TypeNameExtractor typeNameExtractor) {

        this.accessors = accessors;
        this.fields = fields;
        this.factoryMethods = factoryMethods;
        this.typeResolver = typeResolver;
        this.namingStrategy = namingStrategy;
        this.schemaPluginsManager = schemaPluginsManager;
        this.typeNameExtractor = typeNameExtractor;
    }

    @Override
    public void onApplicationEvent(ObjectMapperConfigured event) {
        objectMapper = event.getObjectMapper();
    }

    @Override
    public List<ModelProperty> propertiesFor(ResolvedType type, ModelContext givenContext) {
        List<ModelProperty> properties = newArrayList();
        BeanDescription beanDescription = beanDescription(type, givenContext);
        Map<String, BeanPropertyDefinition> propertyLookup = uniqueIndex(beanDescription.findProperties(),
                BeanPropertyDefinitions.beanPropertyByInternalName());
        for (Map.Entry<String, BeanPropertyDefinition> each : propertyLookup.entrySet()) {
            LOG.debug("Reading property {}", each.getKey());
            BeanPropertyDefinition jacksonProperty = each.getValue();
            Optional<AnnotatedMember> annotatedMember = Optional
                    .fromNullable(safeGetPrimaryMember(jacksonProperty));
            if (annotatedMember.isPresent()) {
                properties.addAll(candidateProperties(type, annotatedMember.get(), jacksonProperty, givenContext));
            }
        }
        return FluentIterable.from(properties).toSortedSet(byPropertyName()).asList();
    }

    private Comparator<ModelProperty> byPropertyName() {
        return new Comparator<ModelProperty>() {
            @Override
            public int compare(ModelProperty first, ModelProperty second) {
                return first.getName().compareTo(second.getName());
            }
        };
    }

    private AnnotatedMember safeGetPrimaryMember(BeanPropertyDefinition jacksonProperty) {
        try {
            return jacksonProperty.getPrimaryMember();
        } catch (IllegalArgumentException e) {
            LOG.warn(String.format("Unable to get unique property. %s", e.getMessage()));
            return null;
        }
    }

    private Function<ResolvedMethod, List<ModelProperty>> propertyFromBean(final ModelContext givenContext,
            final BeanPropertyDefinition jacksonProperty) {

        return new Function<ResolvedMethod, List<ModelProperty>>() {
            @Override
            public List<ModelProperty> apply(ResolvedMethod input) {
                ResolvedType type = paramOrReturnType(typeResolver, input);
                if (!givenContext.canIgnore(type)) {
                    if (shouldUnwrap(input)) {
                        return propertiesFor(type, fromParent(givenContext, type));
                    }
                    return newArrayList(beanModelProperty(input, jacksonProperty, givenContext));
                }
                return newArrayList();
            }
        };
    }

    private boolean shouldUnwrap(ResolvedMethod input) {
        return any(newArrayList(input.getRawMember().getDeclaredAnnotations()), ofType(JsonUnwrapped.class));
    }

    private Function<ResolvedField, List<ModelProperty>> propertyFromField(final ModelContext givenContext,
            final BeanPropertyDefinition jacksonProperty) {

        return new Function<ResolvedField, List<ModelProperty>>() {
            @Override
            public List<ModelProperty> apply(ResolvedField input) {
                List<Annotation> annotations = newArrayList(input.getRawMember().getAnnotations());
                if (!givenContext.canIgnore(input.getType())) {
                    if (any(annotations, ofType(JsonUnwrapped.class))) {
                        return propertiesFor(input.getType(),
                                ModelContext.fromParent(givenContext, input.getType()));
                    }
                    return newArrayList(fieldModelProperty(input, jacksonProperty, givenContext));
                }
                return newArrayList();
            }
        };
    }

    private Predicate<? super Annotation> ofType(final Class<?> annotationType) {
        return new Predicate<Annotation>() {
            @Override
            public boolean apply(Annotation input) {
                return annotationType.isAssignableFrom(input.getClass());
            }
        };
    }

    @VisibleForTesting
    List<ModelProperty> candidateProperties(ResolvedType type, AnnotatedMember member,
            BeanPropertyDefinition jacksonProperty, ModelContext givenContext) {

        List<ModelProperty> properties = newArrayList();
        if (member instanceof AnnotatedMethod) {
            properties.addAll(findAccessorMethod(type, member)
                    .transform(propertyFromBean(givenContext, jacksonProperty)).or(new ArrayList<ModelProperty>()));
        } else if (member instanceof AnnotatedField) {
            properties.addAll(findField(type, jacksonProperty.getInternalName())
                    .transform(propertyFromField(givenContext, jacksonProperty))
                    .or(new ArrayList<ModelProperty>()));
        } else if (member instanceof AnnotatedParameter) {
            ModelContext modelContext = ModelContext.fromParent(givenContext, type);
            properties.addAll(fromFactoryMethod(type, jacksonProperty, (AnnotatedParameter) member, modelContext));
        }
        return from(properties).filter(hiddenProperties()).toList();

    }

    private Predicate<? super ModelProperty> hiddenProperties() {
        return new Predicate<ModelProperty>() {
            @Override
            public boolean apply(ModelProperty input) {
                return !input.isHidden();
            }
        };
    }

    private Optional<ResolvedField> findField(ResolvedType resolvedType, final String fieldName) {

        return tryFind(fields.in(resolvedType), new Predicate<ResolvedField>() {
            public boolean apply(ResolvedField input) {
                return fieldName.equals(input.getName());
            }
        });
    }

    private ModelProperty fieldModelProperty(ResolvedField childField, BeanPropertyDefinition jacksonProperty,
            ModelContext modelContext) {
        String fieldName = name(jacksonProperty, modelContext.isReturnType(), namingStrategy);
        FieldModelProperty fieldModelProperty = new FieldModelProperty(fieldName, childField, typeResolver,
                modelContext.getAlternateTypeProvider(), jacksonProperty);
        ModelPropertyBuilder propertyBuilder = new ModelPropertyBuilder().name(fieldModelProperty.getName())
                .type(fieldModelProperty.getType()).qualifiedType(fieldModelProperty.qualifiedTypeName())
                .position(fieldModelProperty.position()).required(fieldModelProperty.isRequired())
                .description(fieldModelProperty.propertyDescription())
                .allowableValues(fieldModelProperty.allowableValues()).example(fieldModelProperty.example());
        return schemaPluginsManager
                .property(new ModelPropertyContext(propertyBuilder, childField.getRawMember(), typeResolver,
                        modelContext.getDocumentationType()))
                .updateModelRef(modelRefFactory(modelContext, typeNameExtractor));
    }

    private ModelProperty beanModelProperty(ResolvedMethod childProperty, BeanPropertyDefinition jacksonProperty,
            ModelContext modelContext) {

        String propertyName = name(jacksonProperty, modelContext.isReturnType(), namingStrategy);
        BeanModelProperty beanModelProperty = new BeanModelProperty(propertyName, childProperty, typeResolver,
                modelContext.getAlternateTypeProvider(), jacksonProperty);

        LOG.debug("Adding property {} to model", propertyName);
        ModelPropertyBuilder propertyBuilder = new ModelPropertyBuilder().name(beanModelProperty.getName())
                .type(beanModelProperty.getType()).qualifiedType(beanModelProperty.qualifiedTypeName())
                .position(beanModelProperty.position()).required(beanModelProperty.isRequired()).isHidden(false)
                .description(beanModelProperty.propertyDescription())
                .allowableValues(beanModelProperty.allowableValues()).example(beanModelProperty.example());
        return schemaPluginsManager
                .property(new ModelPropertyContext(propertyBuilder, jacksonProperty, typeResolver,
                        modelContext.getDocumentationType()))
                .updateModelRef(modelRefFactory(modelContext, typeNameExtractor));
    }

    private ModelProperty paramModelProperty(ResolvedParameterizedMember constructor,
            BeanPropertyDefinition jacksonProperty, AnnotatedParameter parameter, ModelContext modelContext) {

        String propertyName = name(jacksonProperty, modelContext.isReturnType(), namingStrategy);
        ParameterModelProperty parameterModelProperty = new ParameterModelProperty(propertyName, parameter,
                constructor, typeResolver, modelContext.getAlternateTypeProvider(), jacksonProperty);

        LOG.debug("Adding property {} to model", propertyName);
        ModelPropertyBuilder propertyBuilder = new ModelPropertyBuilder().name(parameterModelProperty.getName())
                .type(parameterModelProperty.getType()).qualifiedType(parameterModelProperty.qualifiedTypeName())
                .position(parameterModelProperty.position()).required(parameterModelProperty.isRequired())
                .isHidden(false).description(parameterModelProperty.propertyDescription())
                .allowableValues(parameterModelProperty.allowableValues())
                .example(parameterModelProperty.example());
        return schemaPluginsManager
                .property(new ModelPropertyContext(propertyBuilder, jacksonProperty, typeResolver,
                        modelContext.getDocumentationType()))
                .updateModelRef(modelRefFactory(modelContext, typeNameExtractor));
    }

    private Optional<ResolvedMethod> findAccessorMethod(ResolvedType resolvedType, final AnnotatedMember member) {
        return tryFind(accessors.in(resolvedType), new Predicate<ResolvedMethod>() {
            public boolean apply(ResolvedMethod accessorMethod) {
                SimpleMethodSignatureEquality methodComparer = new SimpleMethodSignatureEquality();
                return methodComparer.equivalent(accessorMethod.getRawMember(), (Method) member.getMember());
            }
        });
    }

    private List<ModelProperty> fromFactoryMethod(final ResolvedType resolvedType,
            final BeanPropertyDefinition beanProperty, final AnnotatedParameter member,
            final ModelContext givenContext) {

        Optional<ModelProperty> property = factoryMethods.in(resolvedType, factoryMethodOf(member))
                .transform(new Function<ResolvedParameterizedMember, ModelProperty>() {
                    @Override
                    public ModelProperty apply(ResolvedParameterizedMember input) {
                        return paramModelProperty(input, beanProperty, member, givenContext);
                    }
                });
        if (property.isPresent()) {
            return newArrayList(property.get());
        }
        return newArrayList();
    }

    private BeanDescription beanDescription(ResolvedType type, ModelContext context) {
        if (context.isReturnType()) {
            SerializationConfig serializationConfig = objectMapper.getSerializationConfig();
            return serializationConfig
                    .introspect(TypeFactory.defaultInstance().constructType(type.getErasedType()));
        } else {
            DeserializationConfig serializationConfig = objectMapper.getDeserializationConfig();
            return serializationConfig
                    .introspect(TypeFactory.defaultInstance().constructType(type.getErasedType()));
        }
    }
}