Skip to content

Commit 6e709c9

Browse files
committed
Add support for HandlerTypePredicate in spring-web. Fixes #1258
1 parent 313214e commit 6e709c9

File tree

11 files changed

+373
-40
lines changed

11 files changed

+373
-40
lines changed

springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.context.annotation.Bean;
4343
import org.springframework.context.annotation.Configuration;
4444
import org.springframework.context.annotation.Lazy;
45+
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
4546

4647
import static org.springdoc.core.Constants.SPRINGDOC_SWAGGER_UI_ENABLED;
4748
import static org.springdoc.core.Constants.SPRINGDOC_USE_MANAGEMENT_PORT;
@@ -64,13 +65,27 @@ public class SwaggerConfig {
6465
* @param swaggerUiConfig the swagger ui config
6566
* @param springDocConfigProperties the spring doc config properties
6667
* @param swaggerUiConfigParameters the swagger ui config parameters
68+
* @param requestMappingInfoHandlerMappingOptional the request mapping info handler mapping optional
6769
* @return the swagger welcome
6870
*/
6971
@Bean
7072
@ConditionalOnMissingBean
7173
@ConditionalOnProperty(name = SPRINGDOC_USE_MANAGEMENT_PORT, havingValue = "false", matchIfMissing = true)
72-
SwaggerWelcomeWebMvc swaggerWelcome(SwaggerUiConfigProperties swaggerUiConfig, SpringDocConfigProperties springDocConfigProperties, SwaggerUiConfigParameters swaggerUiConfigParameters) {
73-
return new SwaggerWelcomeWebMvc(swaggerUiConfig, springDocConfigProperties,swaggerUiConfigParameters);
74+
SwaggerWelcomeWebMvc swaggerWelcome(SwaggerUiConfigProperties swaggerUiConfig, SpringDocConfigProperties springDocConfigProperties, SwaggerUiConfigParameters swaggerUiConfigParameters, Optional<RequestMappingInfoHandlerMapping> requestMappingInfoHandlerMappingOptional) {
75+
return new SwaggerWelcomeWebMvc(swaggerUiConfig, springDocConfigProperties,swaggerUiConfigParameters, requestMappingInfoHandlerMappingOptional);
76+
}
77+
78+
/**
79+
* Swagger config resource swagger config resource.
80+
*
81+
* @param swaggerWelcomeCommon the swagger welcome common
82+
* @return the swagger config resource
83+
*/
84+
@Bean
85+
@ConditionalOnMissingBean
86+
@ConditionalOnProperty(name = SPRINGDOC_USE_MANAGEMENT_PORT, havingValue = "false", matchIfMissing = true)
87+
SwaggerConfigResource swaggerConfigResource(SwaggerWelcomeCommon swaggerWelcomeCommon){
88+
return new SwaggerConfigResource(swaggerWelcomeCommon);
7489
}
7590

7691
/**
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
*
3+
* *
4+
* * * Copyright 2019-2020 the original author or authors.
5+
* * *
6+
* * * Licensed under the Apache License, Version 2.0 (the "License");
7+
* * * you may not use this file except in compliance with the License.
8+
* * * You may obtain a copy of the License at
9+
* * *
10+
* * * https://www.apache.org/licenses/LICENSE-2.0
11+
* * *
12+
* * * Unless required by applicable law or agreed to in writing, software
13+
* * * distributed under the License is distributed on an "AS IS" BASIS,
14+
* * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* * * See the License for the specific language governing permissions and
16+
* * * limitations under the License.
17+
* *
18+
*
19+
*/
20+
package org.springdoc.webmvc.ui;
21+
22+
import java.util.Map;
23+
24+
import javax.servlet.http.HttpServletRequest;
25+
26+
import io.swagger.v3.oas.annotations.Operation;
27+
28+
import org.springframework.http.MediaType;
29+
import org.springframework.web.bind.annotation.GetMapping;
30+
import org.springframework.web.bind.annotation.RestController;
31+
32+
import static org.springdoc.core.Constants.SWAGGER_CONFIG_URL;
33+
34+
/**
35+
* The type Swagger config resource.
36+
* @author bnasslahsen
37+
*/
38+
@RestController
39+
public class SwaggerConfigResource {
40+
41+
/**
42+
* The Swagger welcome common.
43+
*/
44+
private final SwaggerWelcomeCommon swaggerWelcomeCommon;
45+
46+
/**
47+
* Instantiates a new Swagger config resource.
48+
*
49+
* @param swaggerWelcomeCommon the swagger welcome common
50+
*/
51+
public SwaggerConfigResource(SwaggerWelcomeCommon swaggerWelcomeCommon) {
52+
this.swaggerWelcomeCommon = swaggerWelcomeCommon;
53+
}
54+
55+
/**
56+
* Openapi yaml map.
57+
*
58+
* @param request the request
59+
* @return the map
60+
*/
61+
@Operation(hidden = true)
62+
@GetMapping(value = SWAGGER_CONFIG_URL, produces = MediaType.APPLICATION_JSON_VALUE)
63+
public Map<String, Object> openapiJson(HttpServletRequest request) {
64+
return swaggerWelcomeCommon.openapiJson(request);
65+
}
66+
67+
}

springdoc-openapi-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeWebMvc.java

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,33 @@
2020

2121
package org.springdoc.webmvc.ui;
2222

23+
import java.util.ArrayList;
24+
import java.util.List;
2325
import java.util.Map;
26+
import java.util.Map.Entry;
27+
import java.util.Optional;
28+
import java.util.Set;
2429

30+
import javax.annotation.PostConstruct;
2531
import javax.servlet.http.HttpServletRequest;
2632

2733
import io.swagger.v3.oas.annotations.Operation;
2834
import org.apache.commons.lang3.StringUtils;
2935
import org.springdoc.core.SpringDocConfigProperties;
3036
import org.springdoc.core.SwaggerUiConfigParameters;
3137
import org.springdoc.core.SwaggerUiConfigProperties;
38+
import org.springdoc.webmvc.api.OpenApiResource;
3239

3340
import org.springframework.beans.factory.annotation.Value;
34-
import org.springframework.http.MediaType;
3541
import org.springframework.http.ResponseEntity;
3642
import org.springframework.stereotype.Controller;
43+
import org.springframework.util.CollectionUtils;
3744
import org.springframework.web.bind.annotation.GetMapping;
38-
import org.springframework.web.bind.annotation.ResponseBody;
45+
import org.springframework.web.method.HandlerMethod;
46+
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
47+
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
3948

4049
import static org.springdoc.core.Constants.MVC_SERVLET_PATH;
41-
import static org.springdoc.core.Constants.SWAGGER_CONFIG_URL;
4250
import static org.springdoc.core.Constants.SWAGGER_UI_PATH;
4351
import static org.springdoc.core.Constants.SWAGGGER_CONFIG_FILE;
4452
import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR;
@@ -57,21 +65,54 @@ public class SwaggerWelcomeWebMvc extends SwaggerWelcomeCommon {
5765
@Value(MVC_SERVLET_PATH)
5866
private String mvcServletPath;
5967

68+
/**
69+
* The Path prefix.
70+
*/
71+
private String pathPrefix;
72+
73+
/**
74+
* The Request mapping handler mapping.
75+
*/
76+
private final Optional<RequestMappingInfoHandlerMapping> requestMappingInfoHandlerMappingOptional;
77+
6078
/**
6179
* Instantiates a new Swagger welcome.
6280
*
63-
* @param swaggerUiConfig the swagger ui config
64-
* @param springDocConfigProperties the spring doc config properties
81+
* @param swaggerUiConfig the swagger ui config
82+
* @param springDocConfigProperties the spring doc config properties
6583
* @param swaggerUiConfigParameters the swagger ui config parameters
84+
* @param requestMappingInfoHandlerMappingOptional the request mapping info handler mapping optional
6685
*/
67-
public SwaggerWelcomeWebMvc(SwaggerUiConfigProperties swaggerUiConfig, SpringDocConfigProperties springDocConfigProperties,SwaggerUiConfigParameters swaggerUiConfigParameters) {
86+
public SwaggerWelcomeWebMvc(SwaggerUiConfigProperties swaggerUiConfig, SpringDocConfigProperties springDocConfigProperties,SwaggerUiConfigParameters swaggerUiConfigParameters, Optional<RequestMappingInfoHandlerMapping> requestMappingInfoHandlerMappingOptional) {
6887
super(swaggerUiConfig, springDocConfigProperties, swaggerUiConfigParameters);
88+
this.requestMappingInfoHandlerMappingOptional = requestMappingInfoHandlerMappingOptional;
89+
}
90+
91+
/**
92+
* Init.
93+
*/
94+
@PostConstruct
95+
private void init() {
96+
requestMappingInfoHandlerMappingOptional.ifPresent(requestMappingHandlerMapping -> {
97+
Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
98+
List<Entry<RequestMappingInfo, HandlerMethod>> entries = new ArrayList<>(map.entrySet());
99+
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : entries) {
100+
RequestMappingInfo requestMappingInfo = entry.getKey();
101+
Set<String> patterns = OpenApiResource.getActivePatterns(requestMappingInfo);
102+
if (!CollectionUtils.isEmpty(patterns)) {
103+
for (String operationPath : patterns) {
104+
if (operationPath.endsWith(springDocConfigProperties.getApiDocs().getPath()))
105+
pathPrefix = operationPath.replace(springDocConfigProperties.getApiDocs().getPath(), StringUtils.EMPTY);
106+
}
107+
}
108+
}
109+
});
69110
}
70111

71112
/**
72113
* Redirect to ui string.
73114
*
74-
* @param request the request
115+
* @param request the request
75116
* @return the string
76117
*/
77118
@Operation(hidden = true)
@@ -82,19 +123,10 @@ public ResponseEntity<Void> redirectToUi(HttpServletRequest request) {
82123
}
83124

84125
/**
85-
* Openapi yaml map.
126+
* Calculate ui root path.
86127
*
87-
* @param request the request
88-
* @return the map
128+
* @param sbUrls the sb urls
89129
*/
90-
@Operation(hidden = true)
91-
@GetMapping(value = SWAGGER_CONFIG_URL, produces = MediaType.APPLICATION_JSON_VALUE)
92-
@ResponseBody
93-
@Override
94-
public Map<String, Object> openapiJson(HttpServletRequest request) {
95-
return super.openapiJson(request);
96-
}
97-
98130
@Override
99131
protected void calculateUiRootPath(StringBuilder... sbUrls) {
100132
StringBuilder sbUrl = new StringBuilder();
@@ -103,18 +135,35 @@ protected void calculateUiRootPath(StringBuilder... sbUrls) {
103135
calculateUiRootCommon(sbUrl, sbUrls);
104136
}
105137

138+
/**
139+
* Build url string.
140+
*
141+
* @param contextPath the context path
142+
* @param docsUrl the docs url
143+
* @return the string
144+
*/
106145
@Override
107146
protected String buildUrl(String contextPath, final String docsUrl) {
108147
if (StringUtils.isNotBlank(mvcServletPath))
109148
contextPath += mvcServletPath;
110149
return super.buildUrl(contextPath, docsUrl);
111150
}
112151

152+
/**
153+
* Build api doc url string.
154+
*
155+
* @return the string
156+
*/
113157
@Override
114158
protected String buildApiDocUrl() {
115-
return buildUrl(contextPath, springDocConfigProperties.getApiDocs().getPath());
159+
return buildUrl(contextPath + pathPrefix, springDocConfigProperties.getApiDocs().getPath());
116160
}
117161

162+
/**
163+
* Build swagger config url string.
164+
*
165+
* @return the string
166+
*/
118167
@Override
119168
protected String buildSwaggerConfigUrl() {
120169
return apiDocsUrl + DEFAULT_PATH_SEPARATOR + SWAGGGER_CONFIG_FILE;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package test.org.springdoc.ui.app21;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.web.bind.annotation.RestController;
5+
import org.springframework.web.method.HandlerTypePredicate;
6+
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
7+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
8+
9+
@Configuration
10+
public class AppConfiguration implements WebMvcConfigurer {
11+
12+
@Override
13+
public void configurePathMatch(PathMatchConfigurer configurer) {
14+
configurer.addPathPrefix("/rest",
15+
HandlerTypePredicate.forAnnotation(RestController.class));
16+
}
17+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 test.org.springdoc.ui.app21;
20+
21+
import io.swagger.v3.oas.annotations.Operation;
22+
import io.swagger.v3.oas.annotations.Parameter;
23+
24+
import org.springframework.http.MediaType;
25+
import org.springframework.http.ResponseEntity;
26+
import org.springframework.web.bind.annotation.PostMapping;
27+
import org.springframework.web.bind.annotation.RequestPart;
28+
import org.springframework.web.bind.annotation.RestController;
29+
30+
@RestController
31+
public class HelloController {
32+
33+
34+
@Operation(summary = "add")
35+
@PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
36+
public ResponseEntity<Void> add(@Parameter(description = "content") @RequestPart(value = "content") String content) throws Exception {
37+
return null;
38+
}
39+
40+
41+
@Operation(summary = "add2")
42+
@PostMapping(value = "/add2", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
43+
public ResponseEntity<Void> add2(
44+
@Parameter(description = "content") @RequestPart(value = "content") String content,
45+
@RequestPart(value = "type") String type
46+
) {
47+
return null;
48+
}
49+
50+
@PostMapping( produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
51+
@Operation(summary = "test")
52+
public void test(@RequestPart("strValue") String strValue,
53+
@RequestPart("intValue") Integer intValue) {
54+
}
55+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 test.org.springdoc.ui.app21;
20+
21+
import org.junit.jupiter.api.Test;
22+
import org.springdoc.core.Constants;
23+
import test.org.springdoc.ui.AbstractSpringDocTest;
24+
25+
import org.springframework.beans.factory.annotation.Value;
26+
import org.springframework.boot.autoconfigure.SpringBootApplication;
27+
28+
import static org.hamcrest.CoreMatchers.equalTo;
29+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
30+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
31+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
32+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
33+
34+
public class SpringDocApp21Test extends AbstractSpringDocTest {
35+
36+
@Value(Constants.SWAGGER_UI_VERSION)
37+
private String swaggerUiVersion;
38+
39+
40+
@Test
41+
public void testAddSwaggerUiVersionToPath() throws Exception {
42+
mockMvc.perform(get("/swagger-ui.html"))
43+
.andExpect(status().isFound())
44+
.andExpect(header().string("Location", "/swagger-ui/index.html?configUrl=/rest/v3/api-docs/swagger-config"));
45+
}
46+
47+
@Test
48+
public void shouldRedirectWithPrefix() throws Exception {
49+
mockMvc.perform(get("/rest/v3/api-docs/swagger-config"))
50+
.andExpect(status().isOk())
51+
.andExpect(jsonPath("validatorUrl", equalTo("")))
52+
.andExpect(jsonPath("oauth2RedirectUrl", equalTo("http://localhost/swagger-ui/oauth2-redirect.html")));
53+
}
54+
55+
@SpringBootApplication
56+
static class SpringDocTestApp {
57+
}
58+
59+
}

0 commit comments

Comments
 (0)