diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java index 6f46a4c8bbc..cf4fedaeea8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketServerAutoConfigurationTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.rsocket; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer; import org.springframework.boot.rsocket.server.RSocketServerBootstrap; import org.springframework.boot.rsocket.server.RSocketServerFactory; import org.springframework.boot.rsocket.server.ServerRSocketFactoryCustomizer; @@ -39,6 +40,7 @@ import static org.mockito.Mockito.mock; * Tests for {@link RSocketServerAutoConfiguration}. * * @author Brian Clozel + * @author Verónica Vásquez */ class RSocketServerAutoConfigurationTests { @@ -80,6 +82,17 @@ class RSocketServerAutoConfigurationTests { .hasSingleBean(ServerRSocketFactoryCustomizer.class)); } + @Test + void shouldSetLocalServerPortWhenRSocketServerPortIsSet() { + reactiveWebContextRunner().withPropertyValues("spring.rsocket.server.port=0") + .withInitializer(new RSocketPortInfoApplicationContextInitializer()).run((context) -> { + assertThat(context).hasSingleBean(RSocketServerFactory.class) + .hasSingleBean(RSocketServerBootstrap.class) + .hasSingleBean(ServerRSocketFactoryCustomizer.class); + assertThat(context.getEnvironment().getProperty("local.rsocket.server.port")).isNotNull(); + }); + } + @Test void shouldUseCustomServerBootstrap() { contextRunner().withUserConfiguration(CustomServerBootstrapConfig.class).run((context) -> assertThat(context) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/LocalRSocketServerPort.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/LocalRSocketServerPort.java new file mode 100644 index 00000000000..7da1f6a32c9 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/LocalRSocketServerPort.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2019 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 org.springframework.boot.rsocket.context; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.beans.factory.annotation.Value; + +/** + * Annotation at the field or method/constructor parameter level that injects the RSocket + * port that got allocated at runtime. Provides a convenient alternative for + * @Value("${local.rsocket.server.port}"). + * + * @author Verónica Vásquez + * @author Eddú Meléndez + * @since 2.2.0 + */ +@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Value("${local.rsocket.server.port}") +public @interface LocalRSocketServerPort { + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/RSocketPortInfoApplicationContextInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/RSocketPortInfoApplicationContextInitializer.java new file mode 100644 index 00000000000..09fb5d05f5a --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/rsocket/context/RSocketPortInfoApplicationContextInitializer.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012-2019 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 org.springframework.boot.rsocket.context; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.rsocket.server.RSocketServer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; + +/** + * {@link ApplicationContextInitializer} that sets {@link Environment} properties for the + * ports that {@link RSocketServer} servers are actually listening on. The property + * {@literal "local.rsocket.server.port"} can be injected directly into tests using + * {@link Value @Value} or obtained via the {@link Environment}. + *

+ * Properties are automatically propagated up to any parent context. + * + * @author Verónica Vásquez + * @author Eddú Meléndez + * @since 2.2.0 + */ +public class RSocketPortInfoApplicationContextInitializer + implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + applicationContext.addApplicationListener(new Listener(applicationContext)); + } + + private static class Listener implements ApplicationListener { + + private static final String PROPERTY_NAME = "local.rsocket.server.port"; + + private ConfigurableApplicationContext applicationContext; + + Listener(ConfigurableApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public void onApplicationEvent(RSocketServerInitializedEvent event) { + setPortProperty(this.applicationContext, event.getrSocketServer().address().getPort()); + } + + private void setPortProperty(ApplicationContext context, int port) { + if (context instanceof ConfigurableApplicationContext) { + setPortProperty(((ConfigurableApplicationContext) context).getEnvironment(), port); + } + if (context.getParent() != null) { + setPortProperty(context.getParent(), port); + } + } + + private void setPortProperty(ConfigurableEnvironment environment, int port) { + MutablePropertySources sources = environment.getPropertySources(); + PropertySource source = sources.get("server.ports"); + if (source == null) { + source = new MapPropertySource("server.ports", new HashMap<>()); + sources.addFirst(source); + } + setPortProperty(port, source); + } + + @SuppressWarnings("unchecked") + private void setPortProperty(int port, PropertySource source) { + ((Map) source.getSource()).put(PROPERTY_NAME, port); + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories index cbaf16b4ab1..137c661cf9b 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories @@ -16,6 +16,7 @@ org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ +org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer # Application Listeners diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java index 87f8f2bc8b9..8f374ad1531 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/builder/SpringApplicationBuilderTests.java @@ -240,7 +240,7 @@ class SpringApplicationBuilderTests { SpringApplicationBuilder application = new SpringApplicationBuilder(ExampleConfig.class) .web(WebApplicationType.NONE); this.context = application.run(); - assertThat(application.application().getInitializers()).hasSize(4); + assertThat(application.application().getInitializers()).hasSize(5); } @Test @@ -248,7 +248,7 @@ class SpringApplicationBuilderTests { SpringApplicationBuilder application = new SpringApplicationBuilder(ExampleConfig.class) .child(ChildConfig.class).web(WebApplicationType.NONE); this.context = application.run(); - assertThat(application.application().getInitializers()).hasSize(5); + assertThat(application.application().getInitializers()).hasSize(6); } @Test @@ -257,7 +257,7 @@ class SpringApplicationBuilderTests { .web(WebApplicationType.NONE).initializers((ConfigurableApplicationContext applicationContext) -> { }); this.context = application.run(); - assertThat(application.application().getInitializers()).hasSize(5); + assertThat(application.application().getInitializers()).hasSize(6); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/context/LocalRSocketServerPortTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/context/LocalRSocketServerPortTests.java new file mode 100644 index 00000000000..452a7d8e1d3 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/rsocket/context/LocalRSocketServerPortTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2019 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 org.springframework.boot.rsocket.context; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LocalRSocketServerPort @LocalRSocketServerPort}. + * + * @author Verónica Vásquez + * @author Eddú Meléndez + */ +@ExtendWith(SpringExtension.class) +@TestPropertySource(properties = "local.rsocket.server.port=8181") +class LocalRSocketServerPortTests { + + @Value("${local.rsocket.server.port}") + private String fromValue; + + @LocalRSocketServerPort + private String fromAnnotation; + + @Test + void testLocalRSocketServerPortAnnotation() { + assertThat(this.fromAnnotation).isNotNull().isEqualTo(this.fromValue); + } + + @Configuration(proxyBeanMethods = false) + static class Config { + + } + +}