diff --git a/springwolf-core/build.gradle b/springwolf-core/build.gradle index 9ddd7a706..d75436a25 100644 --- a/springwolf-core/build.gradle +++ b/springwolf-core/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation "org.apache.commons:commons-lang3:${commonsLang3Version}" compileOnly "org.springframework.boot:spring-boot-actuator" + compileOnly "org.springframework:spring-webmvc" compileOnly "org.projectlombok:lombok:${lombokVersion}" annotationProcessor "org.projectlombok:lombok:${lombokVersion}" diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfUiResourceConfiguration.java b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfUiResourceConfiguration.java new file mode 100644 index 000000000..3b6c363a0 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfUiResourceConfiguration.java @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.core.configuration; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@ConditionalOnClass(WebMvcConfigurer.class) +@Import(SpringwolfUiResourceConfigurer.class) +public class SpringwolfUiResourceConfiguration {} diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfUiResourceConfigurer.java b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfUiResourceConfigurer.java new file mode 100644 index 000000000..ccea00426 --- /dev/null +++ b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfUiResourceConfigurer.java @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.core.configuration; + +import io.github.springwolf.core.configuration.properties.SpringwolfConfigProperties; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.web.WebProperties; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Slf4j +@Configuration +@RequiredArgsConstructor +@Order(value = Ordered.HIGHEST_PRECEDENCE) // Highest so that all others will replace this configuration +public class SpringwolfUiResourceConfigurer implements WebMvcConfigurer { + + private final SpringwolfConfigProperties springwolfConfigProperties; + private final WebProperties webProperties; + private final WebMvcProperties webMvcProperties; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + String springwolfBasePath = springwolfConfigProperties.getPath().getBase(); + + log.debug("Serving Springwolf with base-path: {}", springwolfBasePath); + + registry.addResourceHandler(springwolfBasePath + "/**", webMvcProperties.getStaticPathPattern()) + .addResourceLocations(buildStaticLocation()); + } + + private String[] buildStaticLocation() { + List staticLocations = + new ArrayList<>(Arrays.asList(webProperties.getResources().getStaticLocations())); + staticLocations.add("classpath:/META-INF/resources/springwolf/"); + + return staticLocations.toArray(new String[0]); + } +} diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfWebConfiguration.java b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfWebConfiguration.java index 05e1095e8..649f0770e 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfWebConfiguration.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/SpringwolfWebConfiguration.java @@ -13,6 +13,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import static io.github.springwolf.core.configuration.properties.SpringwolfConfigConstants.SPRINGWOLF_ENDPOINT_ACTUATOR_ENABLED; @@ -20,6 +21,7 @@ * Spring-Configuration defining the web controller beans. */ @Configuration(proxyBeanMethods = false) +@Import(SpringwolfUiResourceConfiguration.class) public class SpringwolfWebConfiguration { @Bean diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/properties/SpringwolfConfigProperties.java b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/properties/SpringwolfConfigProperties.java index be8d8836c..a074d2f9a 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/configuration/properties/SpringwolfConfigProperties.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/configuration/properties/SpringwolfConfigProperties.java @@ -56,6 +56,11 @@ public enum InitMode { */ private boolean useFqn = true; + @Deprecated(forRemoval = true) + private Paths paths = new Paths(); + + private Path path = new Path(); + @Nullable private Endpoint endpoint; @@ -67,6 +72,25 @@ public enum InitMode { @Nullable private Payload payload = new Payload(); + @Getter + @Setter + public static class Paths { + + /** + * Deprecated in favor of springwolf.path.docs to control + * only the sub path for the docs endpoint + */ + @Deprecated(forRemoval = true) + private String docs = "/springwolf/docs"; + } + + @Getter + @Setter + public static class Path { + private String base = "/springwolf"; + private String docs = "/docs"; + } + @Getter @Setter public static class ConfigDocket { diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/controller/AsyncApiController.java b/springwolf-core/src/main/java/io/github/springwolf/core/controller/AsyncApiController.java index 66e8ab515..bbf6f986e 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/controller/AsyncApiController.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/controller/AsyncApiController.java @@ -19,7 +19,12 @@ public class AsyncApiController { private final AsyncApiSerializerService serializer; @GetMapping( - path = {"${springwolf.paths.docs:/springwolf/docs}", "${springwolf.paths.docs:/springwolf/docs}.json"}, + path = { + "${springwolf.paths.docs:/springwolf/docs}", + "${springwolf.paths.docs:/springwolf/docs}.json", + "${springwolf.path.base:/springwolf}${springwolf.path.docs:/docs}", + "${springwolf.path.base:/springwolf}${springwolf.path.docs:/docs}.json" + }, produces = MediaType.APPLICATION_JSON_VALUE) public String asyncApiJson() throws JsonProcessingException { log.debug("Returning AsyncApi.json document"); @@ -28,7 +33,12 @@ public String asyncApiJson() throws JsonProcessingException { return serializer.toJsonString(asyncAPI); } - @GetMapping(path = "${springwolf.paths.docs:/springwolf/docs}.yaml", produces = "application/yaml") + @GetMapping( + path = { + "${springwolf.paths.docs:/springwolf/docs}.yaml", + "${springwolf.path.base:/springwolf}${springwolf.path.docs:/docs}.yaml" + }, + produces = "application/yaml") public String asyncApiYaml() throws JsonProcessingException { log.debug("Returning AsyncApi.yaml document"); diff --git a/springwolf-examples/springwolf-sqs-example/src/test/java/io/github/springwolf/examples/sqs/CustomPathConfigurationIntegrationTest.java b/springwolf-examples/springwolf-sqs-example/src/test/java/io/github/springwolf/examples/sqs/CustomPathConfigurationIntegrationTest.java new file mode 100644 index 000000000..6dfafa0d6 --- /dev/null +++ b/springwolf-examples/springwolf-sqs-example/src/test/java/io/github/springwolf/examples/sqs/CustomPathConfigurationIntegrationTest.java @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +package io.github.springwolf.examples.sqs; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.TestPropertySource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringBootTest( + classes = {SpringwolfSqsExampleApplication.class}, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ExtendWith({SqsTestContainerExtension.class}) +@TestPropertySource( + properties = {"springwolf.path.base=/my-custom/springwolf/endpoint/test", "springwolf.path.docs=/apispec"}) +class CustomPathConfigurationIntegrationTest { + + @DynamicPropertySource + static void setUpTestContainers(DynamicPropertyRegistry registry) { + SqsTestContainerExtension.overrideConfiguration(registry); + } + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void getSpec() { + String url = "/my-custom/springwolf/endpoint/test/apispec"; + ResponseEntity responseEntity = restTemplate.getForEntity(url, String.class); + + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + } + + @Test + void canPublish() { + String url = "/my-custom/springwolf/endpoint/test/sqs/publish"; + ResponseEntity responseEntity = restTemplate.getForEntity(url, String.class); + + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + } + + @Test + void asyncapiDocsShouldReturnTheCorrectJsonResponse() { + String url = "/my-custom/springwolf/endpoint/test/asyncapi-ui.html"; + ResponseEntity responseEntity = restTemplate.getForEntity(url, String.class); + + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + } +} diff --git a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/controller/SpringwolfAmqpController.java b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/controller/SpringwolfAmqpController.java index 8a70be637..5171f91d9 100644 --- a/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/controller/SpringwolfAmqpController.java +++ b/springwolf-plugins/springwolf-amqp-plugin/src/main/java/io/github/springwolf/plugins/amqp/controller/SpringwolfAmqpController.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/springwolf/amqp") +@RequestMapping("${springwolf.path.base:/springwolf}/amqp") @Slf4j public class SpringwolfAmqpController extends PublishingBaseController { diff --git a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/controller/SpringwolfJmsController.java b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/controller/SpringwolfJmsController.java index 6ae618150..bab60f5f3 100644 --- a/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/controller/SpringwolfJmsController.java +++ b/springwolf-plugins/springwolf-jms-plugin/src/main/java/io/github/springwolf/plugins/jms/controller/SpringwolfJmsController.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/springwolf/jms") +@RequestMapping("${springwolf.path.base:/springwolf}/jms") @Slf4j public class SpringwolfJmsController extends PublishingBaseController { diff --git a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/controller/SpringwolfKafkaController.java b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/controller/SpringwolfKafkaController.java index 4b3d11294..6f52a20a2 100644 --- a/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/controller/SpringwolfKafkaController.java +++ b/springwolf-plugins/springwolf-kafka-plugin/src/main/java/io/github/springwolf/plugins/kafka/controller/SpringwolfKafkaController.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/springwolf/kafka") +@RequestMapping("${springwolf.path.base:/springwolf}/kafka") @Slf4j public class SpringwolfKafkaController extends PublishingBaseController { diff --git a/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/springwolf/plugins/sns/controller/SpringwolfSnsController.java b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/springwolf/plugins/sns/controller/SpringwolfSnsController.java index 0332337c6..dbf9392dc 100644 --- a/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/springwolf/plugins/sns/controller/SpringwolfSnsController.java +++ b/springwolf-plugins/springwolf-sns-plugin/src/main/java/io/github/springwolf/plugins/sns/controller/SpringwolfSnsController.java @@ -12,7 +12,7 @@ @Slf4j @RestController -@RequestMapping("/springwolf/sns") +@RequestMapping("${springwolf.path.base:/springwolf}/sns") public class SpringwolfSnsController extends PublishingBaseController { private final SpringwolfSnsProducer producer; diff --git a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/controller/SpringwolfSqsController.java b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/controller/SpringwolfSqsController.java index 2d68cbb38..754d5e5df 100644 --- a/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/controller/SpringwolfSqsController.java +++ b/springwolf-plugins/springwolf-sqs-plugin/src/main/java/io/github/springwolf/plugins/sqs/controller/SpringwolfSqsController.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/springwolf/sqs") +@RequestMapping("${springwolf.path.base:/springwolf}/sqs") @Slf4j public class SpringwolfSqsController extends PublishingBaseController {