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.net.URL;
22 import java.util.Comparator;
23 import java.util.HashSet;
24 import java.util.LinkedHashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Objects;
28 import java.util.Set;
29 import java.util.TreeMap;
30 import java.util.stream.Collectors;
31
32 import org.apache.commons.lang3.StringUtils;
33
34 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
35 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
36 import org.springframework.boot.context.properties.ConfigurationProperties;
37 import org.springframework.context.annotation.Configuration;
38 import org.springframework.util.CollectionUtils;
39
40 import static org.springdoc.core.Constants.GROUP_NAME_NOT_NULL;
41 import static org.springdoc.core.Constants.SPRINGDOC_SWAGGER_UI_ENABLED;
42 import static org.springdoc.core.Constants.SWAGGER_UI_OAUTH_REDIRECT_URL;
43 import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
44
45 /**
46  * Please refer to the swagger
47  * <a href="https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md">configuration.md</a>
48  * to get the idea what each parameter does.
49  */

50 @Configuration
51 @ConfigurationProperties(prefix = "springdoc.swagger-ui")
52 @ConditionalOnProperty(name = SPRINGDOC_SWAGGER_UI_ENABLED, matchIfMissing = true)
53 @ConditionalOnBean(SpringDocConfiguration.class)
54 public class SwaggerUiConfigProperties {
55
56     public static final String CONFIG_URL_PROPERTY = "configUrl";
57     public static final String LAYOUT_PROPERTY = "layout";
58     public static final String FILTER_PROPERTY = "filter";
59     /**
60      * The path for the Swagger UI pages to load. Will redirect to the springdoc.webjars.prefix property.
61      */

62     private String path = Constants.DEFAULT_SWAGGER_UI_PATH;
63
64     /**
65      * The name of a component available via the plugin system to use as the top-level layout for Swagger UI.
66      */

67     private String layout;
68
69     /**
70      * URL to fetch external configuration document from.
71      */

72     private String configUrl;
73
74     /**
75      * URL to validate specs against.
76      */

77     private String validatorUrl;
78
79     /**
80      * If set, enables filtering. The top bar will show an edit box that
81      * could be used to filter the tagged operations that are shown.
82      */

83     private String filter;
84
85     /**
86      * Apply a sort to the operation list of each API
87      */

88     private String operationsSorter;
89
90     /**
91      * Apply a sort to the tag list of each API
92      */

93     private String tagsSorter;
94
95     /**
96      * Enables or disables deep linking for tags and operations.
97      *
98      * @see <a href="https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/deep-linking.md">deep-linking.md</a>
99      */

100     private Boolean deepLinking;
101
102     /**
103      * Controls the display of operationId in operations list.
104      */

105     private Boolean displayOperationId;
106
107     /**
108      * The default expansion depth for models (set to -1 completely hide the models).
109      */

110     private Integer defaultModelsExpandDepth;
111
112     /**
113      * The default expansion depth for the model on the model-example section.
114      */

115     private Integer defaultModelExpandDepth;
116
117     /**
118      * Controls how the model is shown when the API is first rendered.
119      */

120     private String defaultModelRendering;
121
122     /**
123      * Controls the display of the request duration (in milliseconds) for Try-It-Out requests.
124      */

125     private Boolean displayRequestDuration;
126
127     /**
128      * Controls the default expansion setting for the operations and tags.
129      */

130     private String docExpansion;
131
132     /**
133      * If set, limits the number of tagged operations displayed to at most this many.
134      */

135     private Integer maxDisplayedTags;
136
137     /**
138      * Controls the display of vendor extension (x-) fields and values.
139      */

140     private Boolean showExtensions;
141
142     /**
143      * Controls the display of extensions
144      */

145     private Boolean showCommonExtensions;
146
147     /**
148      * The supported try it out methods
149      */

150     private List<String> supportedSubmitMethods;
151
152     /**
153      * OAuth redirect URL.
154      */

155     private String oauth2RedirectUrl = SWAGGER_UI_OAUTH_REDIRECT_URL;
156
157     private String url;
158
159     private Set<SwaggerUrl> urls = new HashSet<>();
160
161     private Direction groupsOrder = Direction.ASC;
162
163     public void addGroup(String group) {
164         SwaggerUrl swaggerUrl = new SwaggerUrl(group);
165         urls.add(swaggerUrl);
166     }
167
168     public Set<SwaggerUrl> getUrls() {
169         return this.urls;
170     }
171
172     public void setUrls(Set<SwaggerUrl> urls) {
173         this.urls = urls;
174     }
175
176     public void addUrl(String url) {
177         this.urls.forEach(elt ->
178                 {
179                     if (StringUtils.isBlank(elt.url))
180                         elt.setUrl(url + DEFAULT_PATH_SEPARATOR + elt.getName());
181                 }
182         );
183     }
184
185     public Map<String, Object> getConfigParameters() {
186         final Map<String, Object> params = new TreeMap<>();
187         // empty-string prevents swagger-ui default validation
188         params.put("validatorUrl", validatorUrl != null ? validatorUrl : "");
189         SpringDocPropertiesUtils.put(CONFIG_URL_PROPERTY, configUrl, params);
190         SpringDocPropertiesUtils.put("deepLinking"this.deepLinking, params);
191         SpringDocPropertiesUtils.put("displayOperationId", displayOperationId, params);
192         SpringDocPropertiesUtils.put("defaultModelsExpandDepth", defaultModelsExpandDepth, params);
193         SpringDocPropertiesUtils.put("defaultModelExpandDepth", defaultModelExpandDepth, params);
194         SpringDocPropertiesUtils.put("defaultModelRendering", defaultModelRendering, params);
195         SpringDocPropertiesUtils.put("displayRequestDuration", displayRequestDuration, params);
196         SpringDocPropertiesUtils.put("docExpansion", docExpansion, params);
197         SpringDocPropertiesUtils.put("maxDisplayedTags", maxDisplayedTags, params);
198         SpringDocPropertiesUtils.put("showExtensions", showExtensions, params);
199         SpringDocPropertiesUtils.put("showCommonExtensions", showCommonExtensions, params);
200         SpringDocPropertiesUtils.put("operationsSorter", operationsSorter, params);
201         SpringDocPropertiesUtils.put("tagsSorter", tagsSorter, params);
202         if (!CollectionUtils.isEmpty(supportedSubmitMethods))
203             SpringDocPropertiesUtils.put("supportedSubmitMethods", supportedSubmitMethods.toString(), params);
204         SpringDocPropertiesUtils.put("oauth2RedirectUrl", oauth2RedirectUrl, params);
205         SpringDocPropertiesUtils.put("url", url, params);
206         put("urls", urls, params);
207         return params;
208     }
209
210     public String getValidatorUrl() {
211         return validatorUrl;
212     }
213
214     public void setValidatorUrl(String validatorUrl) {
215         this.validatorUrl = validatorUrl;
216     }
217
218     public String getPath() {
219         return path;
220     }
221
222     public void setPath(String path) {
223         this.path = path;
224     }
225
226     public String getLayout() {
227         return layout;
228     }
229
230     public void setLayout(String layout) {
231         this.layout = layout;
232     }
233
234     public String getConfigUrl() {
235         return configUrl;
236     }
237
238     public void setConfigUrl(String configUrl) {
239         this.configUrl = configUrl;
240     }
241
242     public String getFilter() {
243         return filter;
244     }
245
246     public void setFilter(String filter) {
247         this.filter = filter;
248     }
249
250     public String getOperationsSorter() {
251         return operationsSorter;
252     }
253
254     public void setOperationsSorter(String operationsSorter) {
255         this.operationsSorter = operationsSorter;
256     }
257
258     public String getTagsSorter() {
259         return tagsSorter;
260     }
261
262     public void setTagsSorter(String tagsSorter) {
263         this.tagsSorter = tagsSorter;
264     }
265
266     public Boolean getDeepLinking() {
267         return deepLinking;
268     }
269
270     public void setDeepLinking(Boolean deepLinking) {
271         this.deepLinking = deepLinking;
272     }
273
274     public Boolean getDisplayOperationId() {
275         return displayOperationId;
276     }
277
278     public void setDisplayOperationId(Boolean displayOperationId) {
279         this.displayOperationId = displayOperationId;
280     }
281
282     public Integer getDefaultModelsExpandDepth() {
283         return defaultModelsExpandDepth;
284     }
285
286     public void setDefaultModelsExpandDepth(Integer defaultModelsExpandDepth) {
287         this.defaultModelsExpandDepth = defaultModelsExpandDepth;
288     }
289
290     public Integer getDefaultModelExpandDepth() {
291         return defaultModelExpandDepth;
292     }
293
294     public void setDefaultModelExpandDepth(Integer defaultModelExpandDepth) {
295         this.defaultModelExpandDepth = defaultModelExpandDepth;
296     }
297
298     public String getDefaultModelRendering() {
299         return defaultModelRendering;
300     }
301
302     public void setDefaultModelRendering(String defaultModelRendering) {
303         this.defaultModelRendering = defaultModelRendering;
304     }
305
306     public Boolean getDisplayRequestDuration() {
307         return displayRequestDuration;
308     }
309
310     public void setDisplayRequestDuration(Boolean displayRequestDuration) {
311         this.displayRequestDuration = displayRequestDuration;
312     }
313
314     public String getDocExpansion() {
315         return docExpansion;
316     }
317
318     public void setDocExpansion(String docExpansion) {
319         this.docExpansion = docExpansion;
320     }
321
322     public Integer getMaxDisplayedTags() {
323         return maxDisplayedTags;
324     }
325
326     public void setMaxDisplayedTags(Integer maxDisplayedTags) {
327         this.maxDisplayedTags = maxDisplayedTags;
328     }
329
330     public Boolean getShowExtensions() {
331         return showExtensions;
332     }
333
334     public void setShowExtensions(Boolean showExtensions) {
335         this.showExtensions = showExtensions;
336     }
337
338     public Boolean getShowCommonExtensions() {
339         return showCommonExtensions;
340     }
341
342     public void setShowCommonExtensions(Boolean showCommonExtensions) {
343         this.showCommonExtensions = showCommonExtensions;
344     }
345
346     public List<String> getSupportedSubmitMethods() {
347         return supportedSubmitMethods;
348     }
349
350     public void setSupportedSubmitMethods(List<String> supportedSubmitMethods) {
351         this.supportedSubmitMethods = supportedSubmitMethods;
352     }
353
354     public String getOauth2RedirectUrl() {
355         return oauth2RedirectUrl;
356     }
357
358     public void setOauth2RedirectUrl(String oauth2RedirectUrl) {
359         this.oauth2RedirectUrl = oauth2RedirectUrl;
360     }
361
362     public String getUrl() {
363         return url;
364     }
365
366     public void setUrl(String url) {
367         this.url = url;
368     }
369
370     public boolean isValidUrl(String url) {
371         try {
372             new URL(url).toURI();
373             return true;
374         }
375         catch (Exception e) {
376             return false;
377         }
378     }
379
380     private void put(String urls, Set<SwaggerUrl> swaggerUrls, Map<String, Object> params) {
381         Comparator<SwaggerUrl> swaggerUrlComparator;
382         if (groupsOrder.isAscending())
383             swaggerUrlComparator = Comparator.comparing(SwaggerUrl::getName);
384         else
385             swaggerUrlComparator = (h1, h2) -> h2.getName().compareTo(h1.getName());
386
387         swaggerUrls = swaggerUrls.stream().sorted(swaggerUrlComparator).filter(elt -> StringUtils.isNotEmpty(elt.getUrl())).collect(Collectors.toCollection(LinkedHashSet::new));
388         if (!CollectionUtils.isEmpty(swaggerUrls)) {
389             params.put(urls, swaggerUrls);
390         }
391     }
392
393     public Direction getGroupsOrder() {
394         return groupsOrder;
395     }
396
397     public void setGroupsOrder(Direction groupsOrder) {
398         this.groupsOrder = groupsOrder;
399     }
400
401     enum Direction {
402         ASC,
403         DESC;
404
405         public boolean isAscending() {
406             return this.equals(ASC);
407         }
408     }
409
410     static class SwaggerUrl {
411         private String url;
412
413         private String name;
414
415         public SwaggerUrl() {
416         }
417
418         public SwaggerUrl(String group, String url) {
419             Objects.requireNonNull(group, GROUP_NAME_NOT_NULL);
420             this.url = url;
421             this.name = group;
422         }
423
424         public SwaggerUrl(String group) {
425             Objects.requireNonNull(group, GROUP_NAME_NOT_NULL);
426             this.name = group;
427         }
428
429         public String getUrl() {
430             return url;
431         }
432
433         public void setUrl(String url) {
434             this.url = url;
435         }
436
437         public String getName() {
438             return name;
439         }
440
441         public void setName(String name) {
442             this.name = name;
443         }
444
445         @Override
446         public boolean equals(Object o) {
447             if (this == o) return true;
448             if (o == null || getClass() != o.getClass()) return false;
449             SwaggerUrl that = (SwaggerUrl) o;
450             return name.equals(that.name);
451         }
452
453         @Override
454         public int hashCode() {
455             return Objects.hash(name);
456         }
457
458         @Override
459         public String toString() {
460             final StringBuilder sb = new StringBuilder("SwaggerUrl{");
461             sb.append("url='").append(url).append('\'');
462             sb.append(", name='").append(name).append('\'');
463             sb.append('}');
464             return sb.toString();
465         }
466     }
467 }