1
18
19 package org.springdoc.webmvc.api;
20
21 import java.util.HashSet;
22 import java.util.LinkedHashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Optional;
26 import java.util.Set;
27
28 import javax.servlet.http.HttpServletRequest;
29
30 import com.fasterxml.jackson.core.JsonProcessingException;
31 import io.swagger.v3.core.util.Json;
32 import io.swagger.v3.core.util.PathUtils;
33 import io.swagger.v3.core.util.Yaml;
34 import io.swagger.v3.oas.annotations.Operation;
35 import io.swagger.v3.oas.models.OpenAPI;
36 import org.springdoc.api.AbstractOpenApiResource;
37 import org.springdoc.core.AbstractRequestBuilder;
38 import org.springdoc.core.GenericResponseBuilder;
39 import org.springdoc.core.OpenAPIBuilder;
40 import org.springdoc.core.OperationBuilder;
41 import org.springdoc.core.SecurityOAuth2Provider;
42 import org.springdoc.core.SpringDocConfigProperties;
43 import org.springdoc.core.customizers.OpenApiCustomiser;
44
45 import org.springframework.beans.factory.annotation.Value;
46 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
47 import org.springframework.core.annotation.AnnotationUtils;
48 import org.springframework.http.MediaType;
49 import org.springframework.web.bind.annotation.GetMapping;
50 import org.springframework.web.bind.annotation.RequestMethod;
51 import org.springframework.web.bind.annotation.ResponseBody;
52 import org.springframework.web.bind.annotation.RestController;
53 import org.springframework.web.method.HandlerMethod;
54 import org.springframework.web.servlet.ModelAndView;
55 import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
56 import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
57 import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
58
59 import static org.springdoc.core.Constants.API_DOCS_URL;
60 import static org.springdoc.core.Constants.APPLICATION_OPENAPI_YAML;
61 import static org.springdoc.core.Constants.DEFAULT_API_DOCS_URL_YAML;
62 import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
63
64 @RestController
65 @ConditionalOnMissingBean(name = "openApiResource")
66 public class OpenApiResource extends AbstractOpenApiResource {
67
68 private final RequestMappingInfoHandlerMapping requestMappingHandlerMapping;
69
70 private final Optional<ActuatorProvider> servletContextProvider;
71
72 private final Optional<SecurityOAuth2Provider> springSecurityOAuth2Provider;
73
74 public OpenApiResource(String groupName, OpenAPIBuilder openAPIBuilder, AbstractRequestBuilder requestBuilder,
75 GenericResponseBuilder responseBuilder, OperationBuilder operationParser,
76 RequestMappingInfoHandlerMapping requestMappingHandlerMapping,
77 Optional<ActuatorProvider> servletContextProvider,
78 Optional<List<OpenApiCustomiser>> openApiCustomisers,
79 SpringDocConfigProperties springDocConfigProperties,
80 Optional<SecurityOAuth2Provider> springSecurityOAuth2Provider) {
81 super(groupName, openAPIBuilder, requestBuilder, responseBuilder, operationParser, openApiCustomisers, springDocConfigProperties);
82 this.requestMappingHandlerMapping = requestMappingHandlerMapping;
83 this.servletContextProvider = servletContextProvider;
84 this.springSecurityOAuth2Provider = springSecurityOAuth2Provider;
85 }
86
87 @Operation(hidden = true)
88 @GetMapping(value = API_DOCS_URL, produces = MediaType.APPLICATION_JSON_VALUE)
89 public String openapiJson(HttpServletRequest request, @Value(API_DOCS_URL) String apiDocsUrl)
90 throws JsonProcessingException {
91 calculateServerUrl(request, apiDocsUrl);
92 OpenAPI openAPI = this.getOpenApi();
93 return Json.mapper().writeValueAsString(openAPI);
94 }
95
96 @Operation(hidden = true)
97 @GetMapping(value = DEFAULT_API_DOCS_URL_YAML, produces = APPLICATION_OPENAPI_YAML)
98 public String openapiYaml(HttpServletRequest request, @Value(DEFAULT_API_DOCS_URL_YAML) String apiDocsUrl)
99 throws JsonProcessingException {
100 calculateServerUrl(request, apiDocsUrl);
101 OpenAPI openAPI = this.getOpenApi();
102 return Yaml.mapper().writeValueAsString(openAPI);
103 }
104
105 @Override
106 protected void getPaths(Map<String, Object> restControllers) {
107 Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
108 calculatePath(restControllers, map, Optional.empty());
109
110 if (servletContextProvider.isPresent()) {
111 map = servletContextProvider.get().getMethods();
112 this.openAPIBuilder.addTag(new HashSet<>(map.values()), servletContextProvider.get().getTag());
113 calculatePath(restControllers, map, servletContextProvider);
114 }
115 if (this.springSecurityOAuth2Provider.isPresent()) {
116 SecurityOAuth2Provider securityOAuth2Provider = this.springSecurityOAuth2Provider.get();
117 Map<RequestMappingInfo, HandlerMethod> mapOauth = securityOAuth2Provider.getHandlerMethods();
118 Map<String, Object> requestMappingMapSec = securityOAuth2Provider.getFrameworkEndpoints();
119 Class[] additionalRestClasses = requestMappingMapSec.values().stream().map(Object::getClass).toArray(Class[]::new);
120 AbstractOpenApiResource.addRestControllers(additionalRestClasses);
121 calculatePath(requestMappingMapSec, mapOauth, Optional.empty());
122 }
123 }
124
125 private void calculatePath(Map<String, Object> restControllers, Map<RequestMappingInfo, HandlerMethod> map, Optional<ActuatorProvider> actuatorProvider) {
126 for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
127 RequestMappingInfo requestMappingInfo = entry.getKey();
128 HandlerMethod handlerMethod = entry.getValue();
129 PatternsRequestCondition patternsRequestCondition = requestMappingInfo.getPatternsCondition();
130 Set<String> patterns = patternsRequestCondition.getPatterns();
131 Map<String, String> regexMap = new LinkedHashMap<>();
132 for (String pattern : patterns) {
133 String operationPath = PathUtils.parsePath(pattern, regexMap);
134 if (((actuatorProvider.isPresent() && actuatorProvider.get().isRestController(operationPath))
135 || isRestController(restControllers, handlerMethod, operationPath))
136 && isPackageToScan(handlerMethod.getBeanType().getPackage().getName())
137 && isPathToMatch(operationPath)) {
138 Set<RequestMethod> requestMethods = requestMappingInfo.getMethodsCondition().getMethods();
139
140 if (requestMethods.isEmpty())
141 requestMethods = this.getDefaultAllowedHttpMethods();
142 calculatePath(handlerMethod, operationPath, requestMethods);
143 }
144 }
145 }
146 }
147
148 private boolean isRestController(Map<String, Object> restControllers, HandlerMethod handlerMethod,
149 String operationPath) {
150 ResponseBody responseBodyAnnotation = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), ResponseBody.class);
151 if (responseBodyAnnotation == null)
152 responseBodyAnnotation = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), ResponseBody.class);
153
154 return (responseBodyAnnotation != null && restControllers.containsKey(handlerMethod.getBean().toString()) || isAdditionalRestController(handlerMethod.getBeanType()))
155 && operationPath.startsWith(DEFAULT_PATH_SEPARATOR)
156 && (springDocConfigProperties.isModelAndViewAllowed() || !ModelAndView.class.isAssignableFrom(handlerMethod.getMethod().getReturnType()));
157 }
158
159 private void calculateServerUrl(HttpServletRequest request, String apiDocsUrl) {
160 String requestUrl = decode(request.getRequestURL().toString());
161 String calculatedUrl = requestUrl.substring(0, requestUrl.length() - apiDocsUrl.length());
162 openAPIBuilder.setServerBaseUrl(calculatedUrl);
163 }
164 }
165