1
18
19 package org.springdoc.core;
20
21 import java.io.IOException;
22 import java.lang.reflect.ParameterizedType;
23 import java.lang.reflect.Type;
24 import java.lang.reflect.WildcardType;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Optional;
31
32 import com.fasterxml.jackson.annotation.JsonView;
33 import io.swagger.v3.core.util.AnnotationsUtils;
34 import io.swagger.v3.core.util.Json;
35 import io.swagger.v3.oas.annotations.Hidden;
36 import io.swagger.v3.oas.annotations.enums.Explode;
37 import io.swagger.v3.oas.annotations.media.ExampleObject;
38 import io.swagger.v3.oas.models.Components;
39 import io.swagger.v3.oas.models.examples.Example;
40 import io.swagger.v3.oas.models.media.ArraySchema;
41 import io.swagger.v3.oas.models.media.FileSchema;
42 import io.swagger.v3.oas.models.media.ObjectSchema;
43 import io.swagger.v3.oas.models.media.Schema;
44 import io.swagger.v3.oas.models.parameters.Parameter;
45 import org.apache.commons.lang3.StringUtils;
46
47 import org.springframework.core.MethodParameter;
48 import org.springframework.core.annotation.AnnotationUtils;
49 import org.springframework.http.codec.multipart.FilePart;
50 import org.springframework.web.multipart.MultipartFile;
51
52 @SuppressWarnings("rawtypes")
53 public class GenericParameterBuilder {
54
55 private static final List<Class<?>> FILE_TYPES = new ArrayList<>();
56
57 private static final List<Class> ANNOTATIOSN_TO_IGNORE = new ArrayList<>();
58
59 static {
60 FILE_TYPES.add(MultipartFile.class);
61 ANNOTATIOSN_TO_IGNORE.add(Hidden.class);
62 }
63
64 private final PropertyResolverUtils propertyResolverUtils;
65
66 public GenericParameterBuilder(PropertyResolverUtils propertyResolverUtils) {
67 this.propertyResolverUtils = propertyResolverUtils;
68 }
69
70 public static void addFileType(Class<?>... classes) {
71 FILE_TYPES.addAll(Arrays.asList(classes));
72 }
73
74 public static void addAnnotationsToIgnore(Class<?>... classes) {
75 ANNOTATIOSN_TO_IGNORE.addAll(Arrays.asList(classes));
76 }
77
78 public static void removeAnnotationsToIgnore(Class<?>... classes) {
79 List classesToIgnore = Arrays.asList(classes);
80 if (ANNOTATIOSN_TO_IGNORE.containsAll(classesToIgnore))
81 ANNOTATIOSN_TO_IGNORE.removeAll(Arrays.asList(classes));
82 }
83
84 Parameter mergeParameter(List<Parameter> existingParamDoc, Parameter paramCalcul) {
85 Parameter result = paramCalcul;
86 if (paramCalcul != null && paramCalcul.getName() != null) {
87 final String name = paramCalcul.getName();
88 Parameter paramDoc = existingParamDoc.stream().filter(p -> name.equals(p.getName())).findAny().orElse(null);
89 if (paramDoc != null) {
90 mergeParameter(paramCalcul, paramDoc);
91 result = paramDoc;
92 }
93 else
94 existingParamDoc.add(result);
95 }
96 return result;
97 }
98
99 private void mergeParameter(Parameter paramCalcul, Parameter paramDoc) {
100 if (StringUtils.isBlank(paramDoc.getDescription()))
101 paramDoc.setDescription(paramCalcul.getDescription());
102
103 if (StringUtils.isBlank(paramDoc.getIn()))
104 paramDoc.setIn(paramCalcul.getIn());
105
106 if (paramDoc.getExample() == null)
107 paramDoc.setExample(paramCalcul.getExample());
108
109 if (paramDoc.getDeprecated() == null)
110 paramDoc.setDeprecated(paramCalcul.getDeprecated());
111
112 if (paramDoc.getRequired() == null)
113 paramDoc.setRequired(paramCalcul.getRequired());
114
115 if (paramDoc.getAllowEmptyValue() == null)
116 paramDoc.setAllowEmptyValue(paramCalcul.getAllowEmptyValue());
117
118 if (paramDoc.getAllowReserved() == null)
119 paramDoc.setAllowReserved(paramCalcul.getAllowReserved());
120
121 if (StringUtils.isBlank(paramDoc.get$ref()))
122 paramDoc.set$ref(paramDoc.get$ref());
123
124 if (paramDoc.getSchema() == null)
125 paramDoc.setSchema(paramCalcul.getSchema());
126
127 if (paramDoc.getExamples() == null)
128 paramDoc.setExamples(paramCalcul.getExamples());
129
130 if (paramDoc.getExtensions() == null)
131 paramDoc.setExtensions(paramCalcul.getExtensions());
132
133 if (paramDoc.getStyle() == null)
134 paramDoc.setStyle(paramCalcul.getStyle());
135
136 if (paramDoc.getExplode() == null)
137 paramDoc.setExplode(paramCalcul.getExplode());
138 }
139
140 Parameter buildParameterFromDoc(io.swagger.v3.oas.annotations.Parameter parameterDoc,
141 Components components, JsonView jsonView) {
142 Parameter parameter = new Parameter();
143 if (StringUtils.isNotBlank(parameterDoc.description()))
144 parameter.setDescription(propertyResolverUtils.resolve(parameterDoc.description()));
145 if (StringUtils.isNotBlank(parameterDoc.name()))
146 parameter.setName(propertyResolverUtils.resolve(parameterDoc.name()));
147 if (StringUtils.isNotBlank(parameterDoc.in().toString()))
148 parameter.setIn(parameterDoc.in().toString());
149 if (StringUtils.isNotBlank(parameterDoc.example())) {
150 try {
151 parameter.setExample(Json.mapper().readTree(parameterDoc.example()));
152 }
153 catch (IOException e) {
154 parameter.setExample(parameterDoc.example());
155 }
156 }
157 if (parameterDoc.deprecated())
158 parameter.setDeprecated(parameterDoc.deprecated());
159 if (parameterDoc.required())
160 parameter.setRequired(parameterDoc.required());
161 if (parameterDoc.allowEmptyValue())
162 parameter.setAllowEmptyValue(parameterDoc.allowEmptyValue());
163 if (parameterDoc.allowReserved())
164 parameter.setAllowReserved(parameterDoc.allowReserved());
165
166 setSchema(parameterDoc, components, jsonView, parameter);
167 setExamples(parameterDoc, parameter);
168 setExtensions(parameterDoc, parameter);
169 setParameterStyle(parameter, parameterDoc);
170 setParameterExplode(parameter, parameterDoc);
171
172 return parameter;
173 }
174
175 private void setSchema(io.swagger.v3.oas.annotations.Parameter parameterDoc, Components components, JsonView jsonView, Parameter parameter) {
176 if (StringUtils.isNotBlank(parameterDoc.ref()))
177 parameter.$ref(parameterDoc.ref());
178 else {
179 Schema schema = AnnotationsUtils.getSchemaFromAnnotation(parameterDoc.schema(), components, jsonView).orElse(null);
180 if (schema == null) {
181 if (parameterDoc.content().length > 0) {
182 if (AnnotationsUtils.hasSchemaAnnotation(parameterDoc.content()[0].schema()))
183 schema = AnnotationsUtils.getSchemaFromAnnotation(parameterDoc.content()[0].schema(), components, jsonView).orElse(null);
184 else if (AnnotationsUtils.hasArrayAnnotation(parameterDoc.content()[0].array()))
185 schema = AnnotationsUtils.getArraySchema(parameterDoc.content()[0].array(), components, jsonView).orElse(null);
186 }
187 else
188 schema = AnnotationsUtils.getArraySchema(parameterDoc.array(), components, jsonView).orElse(null);
189 }
190 parameter.setSchema(schema);
191 }
192 }
193
194 Schema calculateSchema(Components components, ParameterInfo parameterInfo, RequestBodyInfo requestBodyInfo, JsonView jsonView) {
195 Schema schemaN;
196 String paramName = parameterInfo.getpName();
197 MethodParameter methodParameter = parameterInfo.getMethodParameter();
198 Class type = methodParameter.getParameterType();
199
200 if (parameterInfo.getParameterModel() == null || parameterInfo.getParameterModel().getSchema() == null) {
201 if (isFile(type)) {
202 schemaN = getFileSchema(requestBodyInfo);
203 schemaN.addProperties(paramName, new FileSchema());
204 return schemaN;
205 }
206 else if (methodParameter.getGenericParameterType() instanceof ParameterizedType) {
207 ParameterizedType parameterizedType = (ParameterizedType) methodParameter.getGenericParameterType();
208 if (isFile(parameterizedType))
209 return extractFileSchema(paramName, requestBodyInfo);
210 schemaN = SpringDocAnnotationsUtils.extractSchema(components, methodParameter.getGenericParameterType(), jsonView, methodParameter.getParameterAnnotations());
211 }
212 else
213 schemaN = SpringDocAnnotationsUtils.resolveSchemaFromType(methodParameter.getParameterType(), components, jsonView, methodParameter.getParameterAnnotations());
214 }
215 else
216 schemaN = parameterInfo.getParameterModel().getSchema();
217
218 if (requestBodyInfo != null) {
219 if (requestBodyInfo.getMergedSchema() != null) {
220 requestBodyInfo.getMergedSchema().addProperties(paramName, schemaN);
221 schemaN = requestBodyInfo.getMergedSchema();
222 }
223 else
224 requestBodyInfo.addProperties(paramName, schemaN);
225 }
226
227 return schemaN;
228 }
229
230 public boolean isFile(MethodParameter methodParameter) {
231 if (methodParameter.getGenericParameterType() instanceof ParameterizedType) {
232 Type type = methodParameter.getGenericParameterType();
233 ParameterizedType parameterizedType = (ParameterizedType) type;
234 return isFile(parameterizedType);
235 }
236 else {
237 Class type = methodParameter.getParameterType();
238 return isFile(type);
239 }
240 }
241
242 public boolean isAnnotationToIgnore(MethodParameter parameter) {
243 return ANNOTATIOSN_TO_IGNORE.stream().anyMatch(
244 annotation -> parameter.getParameterAnnotation(annotation) != null
245 || AnnotationUtils.findAnnotation(parameter.getParameterType(), annotation) != null);
246 }
247
248 private Schema extractFileSchema(String paramName, RequestBodyInfo requestBodyInfo) {
249 Schema schemaN = getFileSchema(requestBodyInfo);
250 ArraySchema schemaFile = new ArraySchema();
251 schemaFile.items(new FileSchema());
252 schemaN.addProperties(paramName, new ArraySchema().items(new FileSchema()));
253 return schemaN;
254 }
255
256 private Schema getFileSchema(RequestBodyInfo requestBodyInfo) {
257 Schema schemaN;
258 if (requestBodyInfo.getMergedSchema() != null)
259 schemaN = requestBodyInfo.getMergedSchema();
260 else {
261 schemaN = new ObjectSchema();
262 requestBodyInfo.setMergedSchema(schemaN);
263 }
264 return schemaN;
265 }
266
267 private boolean isFile(ParameterizedType parameterizedType) {
268 Type type = parameterizedType.getActualTypeArguments()[0];
269 if (MultipartFile.class.getName().equals(type.getTypeName())
270 || FilePart.class.getName().equals(type.getTypeName())) {
271 return true;
272 }
273 else if (type instanceof WildcardType) {
274 WildcardType wildcardType = (WildcardType) type;
275 Type[] upperBounds = wildcardType.getUpperBounds();
276 return MultipartFile.class.getName().equals(upperBounds[0].getTypeName());
277 }
278 return false;
279 }
280
281 private void setExamples(io.swagger.v3.oas.annotations.Parameter parameterDoc, Parameter parameter) {
282 Map<String, Example> exampleMap = new HashMap<>();
283 if (parameterDoc.examples().length == 1 && StringUtils.isBlank(parameterDoc.examples()[0].name())) {
284 Optional<Example> exampleOptional = AnnotationsUtils.getExample(parameterDoc.examples()[0]);
285 exampleOptional.ifPresent(parameter::setExample);
286 }
287 else {
288 for (ExampleObject exampleObject : parameterDoc.examples()) {
289 AnnotationsUtils.getExample(exampleObject)
290 .ifPresent(example -> exampleMap.put(exampleObject.name(), example));
291 }
292 }
293 if (exampleMap.size() > 0) {
294 parameter.setExamples(exampleMap);
295 }
296 }
297
298 private void setExtensions(io.swagger.v3.oas.annotations.Parameter parameterDoc, Parameter parameter) {
299 if (parameterDoc.extensions().length > 0) {
300 Map<String, Object> extensionMap = AnnotationsUtils.getExtensions(parameterDoc.extensions());
301 extensionMap.forEach(parameter::addExtension);
302 }
303 }
304
305 private void setParameterExplode(Parameter parameter, io.swagger.v3.oas.annotations.Parameter p) {
306 if (isExplodable(p)) {
307 if (Explode.TRUE.equals(p.explode())) {
308 parameter.setExplode(Boolean.TRUE);
309 }
310 else if (Explode.FALSE.equals(p.explode())) {
311 parameter.setExplode(Boolean.FALSE);
312 }
313 }
314 }
315
316 private void setParameterStyle(Parameter parameter, io.swagger.v3.oas.annotations.Parameter p) {
317 if (StringUtils.isNotBlank(p.style().toString())) {
318 parameter.setStyle(Parameter.StyleEnum.valueOf(p.style().toString().toUpperCase()));
319 }
320 }
321
322 private boolean isExplodable(io.swagger.v3.oas.annotations.Parameter p) {
323 io.swagger.v3.oas.annotations.media.Schema schema = p.schema();
324 boolean explode = true;
325 Class<?> implementation = schema.implementation();
326 if (implementation == Void.class && !schema.type().equals("object") && !schema.type().equals("array")) {
327 explode = false;
328 }
329 return explode;
330 }
331
332 private boolean isFile(Class type) {
333 return FILE_TYPES.stream().anyMatch(clazz -> clazz.isAssignableFrom(type));
334 }
335 }
336