diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java b/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java index bce53992a..0fe3536d4 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java @@ -295,7 +295,7 @@ public static void addHiddenRestControllers(String... classes) { */ protected synchronized OpenAPI getOpenApi(Locale locale) { OpenAPI openApi; - if (openAPIService.getCachedOpenAPI() == null || springDocConfigProperties.isCacheDisabled()) { + if (openAPIService.getCachedOpenAPI(locale) == null || springDocConfigProperties.isCacheDisabled()) { Instant start = Instant.now(); openAPIService.build(locale); Map mappingsMap = openAPIService.getMappingsMap().entrySet().stream() @@ -335,7 +335,7 @@ protected synchronized OpenAPI getOpenApi(Locale locale) { if (!CollectionUtils.isEmpty(openApi.getServers()) && !openApi.getServers().equals(serversCopy)) openAPIService.setServersPresent(true); - openAPIService.setCachedOpenAPI(openApi); + openAPIService.setCachedOpenAPI(openApi, locale); openAPIService.resetCalculatedOpenAPI(); LOGGER.info("Init duration for springdoc-openapi is: {} ms", @@ -343,7 +343,7 @@ protected synchronized OpenAPI getOpenApi(Locale locale) { } else { LOGGER.debug("Fetching openApi document from cache"); - openApi = openAPIService.updateServers(openAPIService.getCachedOpenAPI()); + openApi = openAPIService.updateServers(openAPIService.getCachedOpenAPI(locale)); } return openApi; } @@ -1109,8 +1109,8 @@ else if (existingOperation != null) { /** * Init open api builder. */ - protected void initOpenAPIBuilder() { - if (openAPIService.getCachedOpenAPI() != null && springDocConfigProperties.isCacheDisabled()) { + protected void initOpenAPIBuilder(Locale locale) { + if (openAPIService.getCachedOpenAPI(locale) != null && springDocConfigProperties.isCacheDisabled()) { openAPIService = openAPIBuilderObjectFactory.getObject(); } } diff --git a/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java b/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java index 8c0696383..308e34ce4 100644 --- a/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java +++ b/springdoc-openapi-common/src/main/java/org/springdoc/core/OpenAPIService.java @@ -129,7 +129,10 @@ public class OpenAPIService { /** * The Cached open api. */ - private OpenAPI cachedOpenAPI; + /** + * The Mappings map. + */ + private final Map cachedOpenAPI = new HashMap<>(); /** * The Calculated open api. @@ -725,19 +728,28 @@ public Map getControllerAdviceMap() { /** * Gets cached open api. * + * @param locale associated the the cache entry * @return the cached open api */ - public OpenAPI getCachedOpenAPI() { - return cachedOpenAPI; + public OpenAPI getCachedOpenAPI(Locale locale) { + Locale cachedLocale = (locale == null ? Locale.getDefault() : locale); + if (cachedOpenAPI != null && cachedOpenAPI.containsKey(cachedLocale.getLanguage())) { + return cachedOpenAPI.get(cachedLocale.getLanguage()); + } + return null; } /** * Sets cached open api. * + * @param locale associated the the cache entry * @param cachedOpenAPI the cached open api */ - public void setCachedOpenAPI(OpenAPI cachedOpenAPI) { - this.cachedOpenAPI = cachedOpenAPI; + public void setCachedOpenAPI(OpenAPI cachedOpenAPI, Locale locale) { + Locale cachedLocale = (locale == null ? Locale.getDefault() : locale); + if (this.cachedOpenAPI != null) { + this.cachedOpenAPI.put(cachedLocale.getLanguage(), cachedOpenAPI); + } } /** diff --git a/springdoc-openapi-common/src/test/java/org/springdoc/api/AbstractOpenApiResourceTest.java b/springdoc-openapi-common/src/test/java/org/springdoc/api/AbstractOpenApiResourceTest.java index bd9629f47..714c74948 100644 --- a/springdoc-openapi-common/src/test/java/org/springdoc/api/AbstractOpenApiResourceTest.java +++ b/springdoc-openapi-common/src/test/java/org/springdoc/api/AbstractOpenApiResourceTest.java @@ -168,10 +168,10 @@ void calculatePathFromRouterOperation() { @Test void preLoadingModeShouldNotOverwriteServers() throws InterruptedException { when(openAPIService.updateServers(any())).thenCallRealMethod(); - when(openAPIService.getCachedOpenAPI()).thenCallRealMethod(); + when(openAPIService.getCachedOpenAPI(any())).thenCallRealMethod(); doAnswer(new CallsRealMethods()).when(openAPIService).setServersPresent(true); doAnswer(new CallsRealMethods()).when(openAPIService).setServerBaseUrl(any()); - doAnswer(new CallsRealMethods()).when(openAPIService).setCachedOpenAPI(any()); + doAnswer(new CallsRealMethods()).when(openAPIService).setCachedOpenAPI(any(), any()); String customUrl = "https://custom.com"; String generatedUrl = "https://generated.com"; @@ -197,8 +197,8 @@ void preLoadingModeShouldNotOverwriteServers() throws InterruptedException { // emulate generating base url openAPIService.setServerBaseUrl(generatedUrl); openAPIService.updateServers(openAPI); - - OpenAPI after = resource.getOpenApi(Locale.getDefault()); + Locale locale = Locale.US; + OpenAPI after = resource.getOpenApi(locale); assertThat(after.getServers().get(0).getUrl(), is(customUrl)); } diff --git a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiActuatorResource.java b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiActuatorResource.java index 256ccecbb..742cf80ae 100644 --- a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiActuatorResource.java +++ b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiActuatorResource.java @@ -122,8 +122,8 @@ public Mono openapiYaml(ServerHttpRequest serverHttpRequest, Locale loca } @Override - protected void calculateServerUrl(ServerHttpRequest serverHttpRequest, String apiDocsUrl) { - super.initOpenAPIBuilder(); + protected void calculateServerUrl(ServerHttpRequest serverHttpRequest, String apiDocsUrl, Locale locale) { + super.initOpenAPIBuilder(locale); URI uri = getActuatorURI(serverHttpRequest.getURI().getScheme(), serverHttpRequest.getURI().getHost()); openAPIService.setServerBaseUrl(uri.toString()); } diff --git a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiResource.java b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiResource.java index bc8ea83b4..8b8f1823b 100644 --- a/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiResource.java +++ b/springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/OpenApiResource.java @@ -122,7 +122,7 @@ public OpenApiResource(ObjectFactory openAPIBuilderObjectFactory */ protected Mono openapiJson(ServerHttpRequest serverHttpRequest, String apiDocsUrl, Locale locale) throws JsonProcessingException { - calculateServerUrl(serverHttpRequest, apiDocsUrl); + calculateServerUrl(serverHttpRequest, apiDocsUrl, locale); OpenAPI openAPI = this.getOpenApi(locale); return Mono.just(writeJsonValue(openAPI)); } @@ -138,7 +138,7 @@ protected Mono openapiJson(ServerHttpRequest serverHttpRequest, String a */ protected Mono openapiYaml(ServerHttpRequest serverHttpRequest, String apiDocsUrl, Locale locale) throws JsonProcessingException { - calculateServerUrl(serverHttpRequest, apiDocsUrl); + calculateServerUrl(serverHttpRequest, apiDocsUrl, locale); OpenAPI openAPI = this.getOpenApi(locale); return Mono.just(writeYamlValue(openAPI)); } @@ -232,8 +232,8 @@ protected void getWebFluxRouterFunctionPaths(Locale locale) { * @param serverHttpRequest the server http request * @param apiDocsUrl the api docs url */ - protected void calculateServerUrl(ServerHttpRequest serverHttpRequest, String apiDocsUrl) { - initOpenAPIBuilder(); + protected void calculateServerUrl(ServerHttpRequest serverHttpRequest, String apiDocsUrl, Locale locale) { + initOpenAPIBuilder(locale); String serverUrl = getServerUrl(serverHttpRequest,apiDocsUrl); openAPIService.setServerBaseUrl(serverUrl); } diff --git a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java index 9f2420453..4cb584f37 100644 --- a/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java +++ b/springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/OpenApiResource.java @@ -157,7 +157,7 @@ public OpenApiResource(ObjectFactory openAPIBuilderObjectFactory public String openapiJson(HttpServletRequest request, String apiDocsUrl, Locale locale) throws JsonProcessingException { - calculateServerUrl(request, apiDocsUrl); + calculateServerUrl(request, apiDocsUrl, locale); OpenAPI openAPI = this.getOpenApi(locale); return writeJsonValue(openAPI); } @@ -174,7 +174,7 @@ public String openapiJson(HttpServletRequest request, public String openapiYaml(HttpServletRequest request, String apiDocsUrl, Locale locale) throws JsonProcessingException { - calculateServerUrl(request, apiDocsUrl); + calculateServerUrl(request, apiDocsUrl, locale); OpenAPI openAPI = this.getOpenApi(locale); return writeYamlValue(openAPI); } @@ -290,8 +290,8 @@ private Comparator> byReversedReque * @param request the request * @param apiDocsUrl the api docs url */ - protected void calculateServerUrl(HttpServletRequest request, String apiDocsUrl) { - super.initOpenAPIBuilder(); + protected void calculateServerUrl(HttpServletRequest request, String apiDocsUrl, Locale locale) { + super.initOpenAPIBuilder(locale); String calculatedUrl = getServerUrl(request, apiDocsUrl); openAPIService.setServerBaseUrl(calculatedUrl); } diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app171/HelloLocaleController.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app171/HelloLocaleController.java new file mode 100644 index 000000000..bb6358ffe --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app171/HelloLocaleController.java @@ -0,0 +1,42 @@ +/* + * + * * Copyright 2019-2020 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * https://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package test.org.springdoc.api.app171; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; + +import org.springframework.http.HttpEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.tags.Tag; + +@RestController +@Tag(name = "greeting", description = "test") +public class HelloLocaleController { + + @GetMapping("/persons") + public void persons(@Valid @NotBlank String name) { + } + + @GetMapping("/test") + public HttpEntity demo2() { + return null; + } +} diff --git a/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app171/SpringDocApp171Test.java b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app171/SpringDocApp171Test.java new file mode 100644 index 000000000..26afbf530 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/java/test/org/springdoc/api/app171/SpringDocApp171Test.java @@ -0,0 +1,62 @@ +/* + * + * * Copyright 2019-2020 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * https://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package test.org.springdoc.api.app171; + +import static org.hamcrest.Matchers.is; +import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.Locale; + +import org.junit.jupiter.api.Test; +import org.springdoc.core.Constants; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.http.HttpHeaders; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.MvcResult; + +import test.org.springdoc.api.AbstractSpringDocTest; + +@TestPropertySource(properties = Constants.SPRINGDOC_CACHE_DISABLED + "=false") +public class SpringDocApp171Test extends AbstractSpringDocTest { + + @SpringBootApplication + static class SpringDocTestApp { + } + + @Test + @Override + public void testApp() throws Exception { + testApp(Locale.US); + testApp(Locale.FRANCE); + } + + private void testApp(Locale locale) throws Exception { + className = getClass().getSimpleName(); + String testNumber = className.replaceAll("[^0-9]", ""); + MvcResult mockMvcResult = + mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL).locale(locale).header(HttpHeaders.ACCEPT_LANGUAGE, locale.toLanguageTag())).andExpect(status().isOk()) + .andExpect(jsonPath("$.openapi", is("3.0.1"))).andReturn(); + String result = mockMvcResult.getResponse().getContentAsString(); + String expected = getContent("results/app" + testNumber + "-" + locale.getLanguage() + ".json"); + assertEquals(expected, result, true); + } +} \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/resources/messages_fr.properties b/springdoc-openapi-webmvc-core/src/test/resources/messages_fr.properties new file mode 100644 index 000000000..be4749154 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/messages_fr.properties @@ -0,0 +1,6 @@ +greeting=Hello! Welcome to our website![FR] +lang.change=Change the language[FR] +lang.eng=English[FR] +lang.fr=French[FR] +mySample=toto[FR] +test=This is a test message[FR] \ No newline at end of file diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/app171-en.json b/springdoc-openapi-webmvc-core/src/test/resources/results/app171-en.json new file mode 100644 index 000000000..16be02d0f --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/app171-en.json @@ -0,0 +1,65 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "tags": [ + { + "name": "Hello! Welcome to our website!", + "description": "This is a test message" + } + ], + "paths": { + "/test": { + "get": { + "tags": [ + "Hello! Welcome to our website!" + ], + "operationId": "demo2", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/persons": { + "get": { + "tags": [ + "Hello! Welcome to our website!" + ], + "operationId": "persons", + "parameters": [ + { + "name": "name", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": {} +} diff --git a/springdoc-openapi-webmvc-core/src/test/resources/results/app171-fr.json b/springdoc-openapi-webmvc-core/src/test/resources/results/app171-fr.json new file mode 100644 index 000000000..42b291f60 --- /dev/null +++ b/springdoc-openapi-webmvc-core/src/test/resources/results/app171-fr.json @@ -0,0 +1,65 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "OpenAPI definition", + "version": "v0" + }, + "servers": [ + { + "url": "http://localhost", + "description": "Generated server url" + } + ], + "tags": [ + { + "name": "Hello! Welcome to our website![FR]", + "description": "This is a test message[FR]" + } + ], + "paths": { + "/test": { + "get": { + "tags": [ + "Hello! Welcome to our website![FR]" + ], + "operationId": "demo2", + "responses": { + "200": { + "description": "OK", + "content": { + "*/*": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/persons": { + "get": { + "tags": [ + "Hello! Welcome to our website![FR]" + ], + "operationId": "persons", + "parameters": [ + { + "name": "name", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "components": {} +}