1 /*
2  *
3  *  * Copyright 2019-2020 the original author or authors.
4  *  *
5  *  * Licensed under the Apache License, Version 2.0 (the "License");
6  *  * you may not use this file except in compliance with the License.
7  *  * You may obtain a copy of the License at
8  *  *
9  *  *      https://www.apache.org/licenses/LICENSE-2.0
10  *  *
11  *  * Unless required by applicable law or agreed to in writing, software
12  *  * distributed under the License is distributed on an "AS IS" BASIS,
13  *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  * See the License for the specific language governing permissions and
15  *  * limitations under the License.
16  *
17  */

18
19 package org.springdoc.core;
20
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Optional;
25 import java.util.stream.Collectors;
26
27 import com.fasterxml.jackson.databind.node.ObjectNode;
28 import io.swagger.v3.core.converter.ModelConverter;
29 import io.swagger.v3.oas.models.Components;
30 import io.swagger.v3.oas.models.OpenAPI;
31 import io.swagger.v3.oas.models.media.ObjectSchema;
32 import io.swagger.v3.oas.models.media.Schema;
33 import org.springdoc.core.converters.AdditionalModelsConverter;
34 import org.springdoc.core.converters.ModelConverterRegistrar;
35 import org.springdoc.core.converters.PropertyCustomizingConverter;
36 import org.springdoc.core.converters.ResponseSupportConverter;
37 import org.springdoc.core.customizers.OpenApiBuilderCustomiser;
38 import org.springdoc.core.customizers.OpenApiCustomiser;
39 import org.springdoc.core.customizers.PropertyCustomizer;
40
41 import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
42 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
43 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
44 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
45 import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
46 import org.springframework.boot.context.properties.bind.BindResult;
47 import org.springframework.boot.context.properties.bind.Binder;
48 import org.springframework.context.ApplicationContext;
49 import org.springframework.context.annotation.Bean;
50 import org.springframework.context.annotation.Conditional;
51 import org.springframework.context.annotation.Configuration;
52 import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
53 import org.springframework.core.env.Environment;
54 import org.springframework.util.CollectionUtils;
55
56 import static org.springdoc.core.Constants.SPRINGDOC_ENABLED;
57 import static org.springdoc.core.Constants.SPRINGDOC_PREFIX;
58 import static org.springdoc.core.Constants.SPRINGDOC_SCHEMA_RESOLVE_PROPERTIES;
59 import static org.springdoc.core.SpringDocUtils.getConfig;
60 import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
61
62 @Configuration
63 @ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true)
64 public class SpringDocConfiguration {
65
66     static {
67         getConfig().replaceWithSchema(ObjectNode.classnew ObjectSchema());
68     }
69
70     @Bean
71     LocalVariableTableParameterNameDiscoverer localSpringDocParameterNameDiscoverer() {
72         return new LocalVariableTableParameterNameDiscoverer();
73     }
74
75     @Bean
76     AdditionalModelsConverter pageableSupportConverter() {
77         return new AdditionalModelsConverter();
78     }
79
80     @Bean
81     PropertyCustomizingConverter propertyCustomizingConverter(Optional<List<PropertyCustomizer>> customizers) {
82         return new PropertyCustomizingConverter(customizers);
83     }
84
85     @Bean
86     @ConditionalOnMissingBean
87     ResponseSupportConverter responseSupportConverter() {
88         return new ResponseSupportConverter();
89     }
90
91     @Bean
92     @ConditionalOnMissingBean
93     OpenAPIBuilder openAPIBuilder(Optional<OpenAPI> openAPI, ApplicationContext context,
94             SecurityParser securityParser,
95             SpringDocConfigProperties springDocConfigProperties,
96             Optional<List<OpenApiBuilderCustomiser>> openApiBuilderCustomisers) {
97         return new OpenAPIBuilder(openAPI, context, securityParser, springDocConfigProperties, openApiBuilderCustomisers);
98     }
99
100     @Bean
101     ModelConverterRegistrar modelConverterRegistrar(Optional<List<ModelConverter>> modelConverters) {
102         return new ModelConverterRegistrar(modelConverters.orElse(Collections.emptyList()));
103     }
104
105     @Bean
106     @ConditionalOnWebApplication
107     @ConditionalOnMissingBean
108     OperationBuilder operationBuilder(GenericParameterBuilder parameterBuilder, RequestBodyBuilder requestBodyBuilder,
109             SecurityParser securityParser, PropertyResolverUtils propertyResolverUtils) {
110         return new OperationBuilder(parameterBuilder, requestBodyBuilder,
111                 securityParser, propertyResolverUtils);
112     }
113
114     @Bean
115     PropertyResolverUtils propertyResolverUtils(ConfigurableBeanFactory factory) {
116         return new PropertyResolverUtils(factory);
117     }
118
119     @Bean
120     @ConditionalOnWebApplication
121     @ConditionalOnMissingBean
122     RequestBodyBuilder requestBodyBuilder(GenericParameterBuilder parameterBuilder) {
123         return new RequestBodyBuilder(parameterBuilder);
124     }
125
126     @Bean
127     @ConditionalOnMissingBean
128     SecurityParser securityParser(PropertyResolverUtils propertyResolverUtils) {
129         return new SecurityParser(propertyResolverUtils);
130     }
131
132     @Bean
133     ReturnTypeParser genericReturnTypeParser() {
134         return new ReturnTypeParser() {};
135     }
136
137     @Bean
138     @ConditionalOnMissingBean
139     GenericParameterBuilder parameterBuilder(PropertyResolverUtils propertyResolverUtils) {
140         return new GenericParameterBuilder(propertyResolverUtils);
141     }
142
143     @Bean
144     @ConditionalOnProperty(SPRINGDOC_SCHEMA_RESOLVE_PROPERTIES)
145     OpenApiCustomiser propertiesResolverForSchema(PropertyResolverUtils propertyResolverUtils, OpenAPIBuilder openAPIBuilder) {
146         return openApi -> {
147             Components components = openApi.getComponents();
148             Map<String, Schema> schemas = components.getSchemas();
149             schemas.values().forEach(schema -> openAPIBuilder.resolveProperties(schema, propertyResolverUtils));
150         };
151     }
152
153     @Bean
154     @Conditional(CacheOrGroupedOpenApiCondition.class)
155     BeanFactoryPostProcessor springdocBeanFactoryPostProcessor(Environment environment) {
156         return beanFactory -> {
157             final BindResult<SpringDocConfigProperties> result = Binder.get(environment)
158                     .bind(SPRINGDOC_PREFIX, SpringDocConfigProperties.class);
159             if (result.isBound()) {
160                 SpringDocConfigProperties springDocGroupConfig = result.get();
161                 List<GroupedOpenApi> groupedOpenApis = springDocGroupConfig.getGroupConfigs().stream()
162                         .map(elt -> {
163                             GroupedOpenApi.Builder builder = GroupedOpenApi.builder();
164                             if (!CollectionUtils.isEmpty(elt.getPackagesToScan()))
165                                 builder.packagesToScan(elt.getPackagesToScan().toArray(new String[0]));
166                             if (!CollectionUtils.isEmpty(elt.getPathsToMatch()))
167                                 builder.pathsToMatch(elt.getPathsToMatch().toArray(new String[0]));
168                             return builder.setGroup(elt.getGroup()).build();
169                         })
170                         .collect(Collectors.toList());
171                 groupedOpenApis.forEach(elt -> beanFactory.registerSingleton(elt.getGroup(), elt));
172             }
173             for (String beanName : beanFactory.getBeanNamesForType(OpenAPIBuilder.class))
174                 beanFactory.getBeanDefinition(beanName).setScope(SCOPE_PROTOTYPE);
175             for (String beanName : beanFactory.getBeanNamesForType(OpenAPI.class))
176                 beanFactory.getBeanDefinition(beanName).setScope(SCOPE_PROTOTYPE);
177         };
178     }
179 }
180