From b9be826c9cd3b50a3df5f6ffb918cf158474957f Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Tue, 12 Sep 2023 08:08:23 +0300 Subject: [PATCH] Optionally close context after command logic - spring.shell.context.close=true adds ApplicationListerner which attempts to close context with ApplicationReadyEvent. - Backport #863 - Relates #866 --- .../ApplicationRunnerAutoConfiguration.java | 21 ++++++++- .../shell/boot/SpringShellProperties.java | 22 ++++++++++ ...plicationRunnerAutoConfigurationTests.java | 43 +++++++++++++++++++ .../boot/SpringShellPropertiesTests.java | 3 ++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ApplicationRunnerAutoConfigurationTests.java diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ApplicationRunnerAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ApplicationRunnerAutoConfiguration.java index afae4ea00..0e4aa9549 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ApplicationRunnerAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ApplicationRunnerAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 the original author or authors. + * Copyright 2021-2023 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. @@ -19,7 +19,10 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.shell.DefaultShellApplicationRunner; import org.springframework.shell.ShellApplicationRunner; @@ -34,4 +37,20 @@ public class ApplicationRunnerAutoConfiguration { public DefaultShellApplicationRunner defaultShellApplicationRunner(List shellRunners) { return new DefaultShellApplicationRunner(shellRunners); } + + @Bean + @ConditionalOnProperty(prefix = "spring.shell.context", name = "close", havingValue = "true") + public ApplicationReadyEventListener applicationReadyEventListener() { + return new ApplicationReadyEventListener(); + } + + static class ApplicationReadyEventListener implements ApplicationListener{ + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + // request context close after application runners so that + // shell exits in case that context is kept alive + event.getApplicationContext().close(); + } + } } diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java index 906692071..376796caf 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java @@ -34,6 +34,7 @@ public class SpringShellProperties { private Command command = new Command(); private Help help = new Help(); private Option option = new Option(); + private Context context = new Context(); public void setConfig(Config config) { this.config = config; @@ -107,6 +108,14 @@ public void setOption(Option option) { this.option = option; } + public Context getContext() { + return context; + } + + public void setContext(Context context) { + this.context = context; + } + public static class Config { private String env; @@ -603,6 +612,19 @@ public void setCaseType(OptionNamingCase caseType) { } } + public static class Context { + + private boolean close = false; + + public boolean isClose() { + return close; + } + + public void setClose(boolean close) { + this.close = close; + } + } + public static enum OptionNamingCase { NOOP, CAMEL, diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ApplicationRunnerAutoConfigurationTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ApplicationRunnerAutoConfigurationTests.java new file mode 100644 index 000000000..4c8fe3d7e --- /dev/null +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/ApplicationRunnerAutoConfigurationTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 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.shell.boot; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ApplicationRunnerAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ApplicationRunnerAutoConfiguration.class)); + + @Test + void contextCloseDisabledByDefault() { + contextRunner.run(context -> assertThat(context) + .doesNotHaveBean(ApplicationRunnerAutoConfiguration.ApplicationReadyEventListener.class)); + } + + @Test + void contextCloseEnabled() { + contextRunner.withPropertyValues("spring.shell.context.close:true") + .run(context -> assertThat(context) + .hasSingleBean(ApplicationRunnerAutoConfiguration.ApplicationReadyEventListener.class)); + } + +} diff --git a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java index 249ffcc91..d482a10a4 100644 --- a/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java @@ -70,6 +70,7 @@ public void defaultNoPropertiesSet() { assertThat(properties.getHelp().getLongNames()).containsExactly("help"); assertThat(properties.getHelp().getShortNames()).containsExactly('h'); assertThat(properties.getOption().getNaming().getCaseType()).isEqualTo(OptionNamingCase.NOOP); + assertThat(properties.getContext().isClose()).isFalse(); }); } @@ -112,6 +113,7 @@ public void setProperties() { .withPropertyValues("spring.shell.help.long-names=fake") .withPropertyValues("spring.shell.help.short-names=f") .withPropertyValues("spring.shell.option.naming.case-type=camel") + .withPropertyValues("spring.shell.context.close=true") .withUserConfiguration(Config1.class) .run((context) -> { SpringShellProperties properties = context.getBean(SpringShellProperties.class); @@ -151,6 +153,7 @@ public void setProperties() { assertThat(properties.getHelp().getLongNames()).containsExactly("fake"); assertThat(properties.getHelp().getShortNames()).containsExactly('f'); assertThat(properties.getOption().getNaming().getCaseType()).isEqualTo(OptionNamingCase.CAMEL); + assertThat(properties.getContext().isClose()).isTrue(); }); }