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 ed591d73..e14d268a 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 @@ -15,46 +15,23 @@ */ package org.springframework.shell.boot; -import org.jline.reader.LineReader; -import org.jline.reader.Parser; +import java.util.List; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; -import org.springframework.shell.Shell; -import org.springframework.shell.jline.InteractiveShellApplicationRunner; -import org.springframework.shell.jline.PromptProvider; -import org.springframework.shell.jline.ScriptShellApplicationRunner; - -import static org.springframework.shell.jline.InteractiveShellApplicationRunner.SPRING_SHELL_INTERACTIVE; -import static org.springframework.shell.jline.ScriptShellApplicationRunner.SPRING_SHELL_SCRIPT; +import org.springframework.shell.DefaultApplicationRunner; +import org.springframework.shell.ShellApplicationRunner; +import org.springframework.shell.ShellRunner; @Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(SpringShellProperties.class) public class ApplicationRunnerAutoConfiguration { - private Shell shell; - - private PromptProvider promptProvider; - - private LineReader lineReader; - - public ApplicationRunnerAutoConfiguration(Shell shell, PromptProvider promptProvider, LineReader lineReader) { - this.shell = shell; - this.promptProvider = promptProvider; - this.lineReader = lineReader; - } - @Bean - @ConditionalOnProperty(prefix = SPRING_SHELL_INTERACTIVE, value = InteractiveShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true) - public InteractiveShellApplicationRunner interactiveApplicationRunner(Environment environment) { - return new InteractiveShellApplicationRunner(lineReader, promptProvider, shell, environment); - } - - @Bean - @ConditionalOnProperty(prefix = SPRING_SHELL_SCRIPT, value = ScriptShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true) - public ScriptShellApplicationRunner scriptApplicationRunner(Parser parser, ConfigurableEnvironment environment) { - return new ScriptShellApplicationRunner(parser, shell, environment); + @ConditionalOnMissingBean(ShellApplicationRunner.class) + public DefaultApplicationRunner defaultApplicationRunner(List shellRunners) { + return new DefaultApplicationRunner(shellRunners); } } diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ShellRunnerAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ShellRunnerAutoConfiguration.java new file mode 100644 index 00000000..12f55284 --- /dev/null +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ShellRunnerAutoConfiguration.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021 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.jline.reader.LineReader; +import org.jline.reader.Parser; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.shell.Shell; +import org.springframework.shell.jline.InteractiveShellApplicationRunner; +import org.springframework.shell.jline.NonInteractiveShellApplicationRunner; +import org.springframework.shell.jline.PromptProvider; +import org.springframework.shell.jline.ScriptShellApplicationRunner; + +@Configuration(proxyBeanMethods = false) +public class ShellRunnerAutoConfiguration { + + private Shell shell; + private PromptProvider promptProvider; + private LineReader lineReader; + private Parser parser; + + public ShellRunnerAutoConfiguration(Shell shell, PromptProvider promptProvider, LineReader lineReader, Parser parser) { + this.shell = shell; + this.promptProvider = promptProvider; + this.lineReader = lineReader; + this.parser = parser; + } + + @Bean + @ConditionalOnProperty(prefix = "spring.shell.interactive", value = "enabled", havingValue = "true", matchIfMissing = true) + public InteractiveShellApplicationRunner interactiveApplicationRunner() { + return new InteractiveShellApplicationRunner(lineReader, promptProvider, shell); + } + + @Bean + @ConditionalOnProperty(prefix = "spring.shell.noninteractive", value = "enabled", havingValue = "true", matchIfMissing = true) + public NonInteractiveShellApplicationRunner nonInteractiveApplicationRunner() { + return new NonInteractiveShellApplicationRunner(shell); + } + + @Bean + @ConditionalOnProperty(prefix = "spring.shell.script", value = "enabled", havingValue = "true", matchIfMissing = true) + public ScriptShellApplicationRunner scriptApplicationRunner() { + return new ScriptShellApplicationRunner(parser, shell); + } +} 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 new file mode 100644 index 00000000..7aa18b13 --- /dev/null +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/SpringShellProperties.java @@ -0,0 +1,239 @@ +/* + * Copyright 2021 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.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for shell. + * + * @author Janne Valkealahti + */ +@ConfigurationProperties(prefix = "spring.shell") +public class SpringShellProperties { + + private Script script = new Script(); + private Interactive interactive = new Interactive(); + private Noninteractive noninteractive = new Noninteractive(); + private Command command = new Command(); + + public void setScript(Script script) { + this.script = script; + } + + public Script getScript() { + return script; + } + + public void setInteractive(Interactive interactive) { + this.interactive = interactive; + } + + public Interactive getInteractive() { + return interactive; + } + + public Noninteractive getNoninteractive() { + return noninteractive; + } + + public void setNoninteractive(Noninteractive noninteractive) { + this.noninteractive = noninteractive; + } + + public Command getCommand() { + return command; + } + + public void setCommand(Command command) { + this.command = command; + } + + public static class Script { + + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class Interactive { + + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class Noninteractive { + + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class HelpCommand { + + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class ClearCommand { + + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class QuitCommand { + + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class StacktraceCommand { + + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class ScriptCommand { + + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class HistoryCommand { + + private boolean enabled = true; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + public static class Command { + + private HelpCommand help = new HelpCommand(); + private ClearCommand clear = new ClearCommand(); + private QuitCommand quit = new QuitCommand(); + private StacktraceCommand stacktrace = new StacktraceCommand(); + private ScriptCommand script = new ScriptCommand(); + private HistoryCommand history = new HistoryCommand(); + + public void setHelp(HelpCommand help) { + this.help = help; + } + + public HelpCommand getHelp() { + return help; + } + + public ClearCommand getClear() { + return clear; + } + + public void setClear(ClearCommand clear) { + this.clear = clear; + } + + public QuitCommand getQuit() { + return quit; + } + + public void setQuit(QuitCommand quit) { + this.quit = quit; + } + + public StacktraceCommand getStacktrace() { + return stacktrace; + } + + public void setStacktrace(StacktraceCommand stacktrace) { + this.stacktrace = stacktrace; + } + + public ScriptCommand getScript() { + return script; + } + + public void setScript(ScriptCommand script) { + this.script = script; + } + + public HistoryCommand getHistory() { + return history; + } + + public void setHistory(HistoryCommand history) { + this.history = history; + } + } +} diff --git a/spring-shell-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-shell-autoconfigure/src/main/resources/META-INF/spring.factories index 79efc39c..49dc20f1 100644 --- a/spring-shell-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-shell-autoconfigure/src/main/resources/META-INF/spring.factories @@ -1,5 +1,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.shell.boot.SpringShellAutoConfiguration,\ +org.springframework.shell.boot.ShellRunnerAutoConfiguration,\ org.springframework.shell.boot.ApplicationRunnerAutoConfiguration,\ org.springframework.shell.boot.CommandRegistryAutoConfiguration,\ org.springframework.shell.boot.LineReaderAutoConfiguration,\ 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 new file mode 100644 index 00000000..6b1b38cf --- /dev/null +++ b/spring-shell-autoconfigure/src/test/java/org/springframework/shell/boot/SpringShellPropertiesTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2021 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 java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.env.SystemEnvironmentPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SpringShellPropertiesTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + + @Test + public void defaultNoPropertiesSet() { + this.contextRunner + .withUserConfiguration(Config1.class) + .run((context) -> { + SpringShellProperties properties = context.getBean(SpringShellProperties.class); + assertThat(properties.getScript().isEnabled()).isTrue(); + assertThat(properties.getInteractive().isEnabled()).isTrue(); + assertThat(properties.getNoninteractive().isEnabled()).isTrue(); + assertThat(properties.getCommand().getClear().isEnabled()).isTrue(); + assertThat(properties.getCommand().getHelp().isEnabled()).isTrue(); + assertThat(properties.getCommand().getHistory().isEnabled()).isTrue(); + assertThat(properties.getCommand().getQuit().isEnabled()).isTrue(); + assertThat(properties.getCommand().getScript().isEnabled()).isTrue(); + assertThat(properties.getCommand().getStacktrace().isEnabled()).isTrue(); + }); + } + + @Test + public void setProperties() { + this.contextRunner + .withInitializer(context -> { + Map map = new HashMap<>(); + map.put("spring.shell.script.enabled", "false"); + map.put("spring.shell.interactive.enabled", "false"); + map.put("spring.shell.noninteractive.enabled", "false"); + map.put("spring.shell.command.clear.enabled", "false"); + map.put("spring.shell.command.help.enabled", "false"); + map.put("spring.shell.command.history.enabled", "false"); + map.put("spring.shell.command.quit.enabled", "false"); + map.put("spring.shell.command.script.enabled", "false"); + map.put("spring.shell.command.stacktrace.enabled", "false"); + context.getEnvironment().getPropertySources().addLast(new SystemEnvironmentPropertySource( + StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map)); + }) + .withUserConfiguration(Config1.class) + .run((context) -> { + SpringShellProperties properties = context.getBean(SpringShellProperties.class); + assertThat(properties.getScript().isEnabled()).isFalse(); + assertThat(properties.getInteractive().isEnabled()).isFalse(); + assertThat(properties.getNoninteractive().isEnabled()).isFalse(); + assertThat(properties.getCommand().getClear().isEnabled()).isFalse(); + assertThat(properties.getCommand().getHelp().isEnabled()).isFalse(); + assertThat(properties.getCommand().getHistory().isEnabled()).isFalse(); + assertThat(properties.getCommand().getQuit().isEnabled()).isFalse(); + assertThat(properties.getCommand().getScript().isEnabled()).isFalse(); + assertThat(properties.getCommand().getStacktrace().isEnabled()).isFalse(); + }); + } + + @EnableConfigurationProperties({ SpringShellProperties.class }) + private static class Config1 { + } +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/DefaultApplicationRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/DefaultApplicationRunner.java new file mode 100644 index 00000000..48d2bd42 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/DefaultApplicationRunner.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021 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; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; + +/** + * Default {@link ApplicationRunner} which dispatches to first ordered + * {@link ShellRunner} able to handle shell. + * + * @author Janne Valkealahti + */ +public class DefaultApplicationRunner implements ShellApplicationRunner { + + private final static Logger log = LoggerFactory.getLogger(DefaultApplicationRunner.class); + private final List shellRunners; + + public DefaultApplicationRunner(List shellRunners) { + // TODO: follow up with spring-native + // Looks like with fatjar it comes on a correct order from + // a context(not really sure if that's how spring context works) but + // not with native, so call AnnotationAwareOrderComparator manually. + Collections.sort(shellRunners, new AnnotationAwareOrderComparator()); + this.shellRunners = shellRunners; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + log.debug("Checking shell runners {}", shellRunners); + Optional optional = shellRunners.stream() + .filter(sh -> sh.canRun(args)) + .findFirst(); + ShellRunner shellRunner = optional.orElse(null); + log.debug("Using shell runner {}", shellRunner); + if (shellRunner != null) { + shellRunner.run(args); + } + } +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/ShellApplicationRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/ShellApplicationRunner.java new file mode 100644 index 00000000..2fabc371 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/ShellApplicationRunner.java @@ -0,0 +1,26 @@ +/* + * Copyright 2021 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; + +import org.springframework.boot.ApplicationRunner; + +/** + * Marker interface for a main spring shell {@link ApplicationRunner}. + * + * @author Janne Valkealahti + */ +public interface ShellApplicationRunner extends ApplicationRunner { +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/ShellRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/ShellRunner.java new file mode 100644 index 00000000..f7707d39 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/ShellRunner.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021 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; + +import org.springframework.boot.ApplicationArguments; + +/** + * Interface for shell runners. + * + * @author Janne Valkealahti + */ +public interface ShellRunner { + + /** + * Checks if a particular shell runner can execute. + * + * @param args the application argumets + * @return true if shell runner can execute + */ + boolean canRun(ApplicationArguments args); + + /** + * Execute application. + * + * @param args the application argumets + * @throws Exception in errors + */ + void run(ApplicationArguments args) throws Exception; +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellApplicationRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellApplicationRunner.java index 289be2cc..7dbdbceb 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellApplicationRunner.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellApplicationRunner.java @@ -16,34 +16,29 @@ package org.springframework.shell.jline; -import java.util.Collections; - import org.jline.reader.LineReader; import org.jline.reader.UserInterruptException; import org.jline.utils.AttributedString; import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; import org.springframework.core.annotation.Order; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; -import org.springframework.core.env.MapPropertySource; import org.springframework.shell.ExitRequest; import org.springframework.shell.Input; import org.springframework.shell.InputProvider; import org.springframework.shell.Shell; +import org.springframework.shell.ShellRunner; /** - * Default Boot runner that bootstraps the shell application in interactive mode. + * Default Boot runner that bootstraps the shell application in interactive + * mode. * - *

- * Runs the REPL of the shell unless the {@literal spring.shell.interactive} property has been set to {@literal false}. - *

+ * Runs the REPL of the shell unless the {@literal spring.shell.interactive} + * property has been set to {@literal false}. * * @author Eric Bottard */ @Order(InteractiveShellApplicationRunner.PRECEDENCE) -public class InteractiveShellApplicationRunner implements ApplicationRunner { +public class InteractiveShellApplicationRunner implements ShellRunner { /** * The precedence at which this runner is set. Highger precedence runners may effectively disable this one by setting @@ -51,47 +46,27 @@ public class InteractiveShellApplicationRunner implements ApplicationRunner { */ public static final int PRECEDENCE = 0; - public static final String SPRING_SHELL_INTERACTIVE = "spring.shell.interactive"; - public static final String ENABLED = "enabled"; - - /** The name of the property that controls whether this runner effectively does something. */ - public static final String SPRING_SHELL_INTERACTIVE_ENABLED = SPRING_SHELL_INTERACTIVE + "." + ENABLED; - private final LineReader lineReader; private final PromptProvider promptProvider; private final Shell shell; - private final Environment environment; - - public InteractiveShellApplicationRunner(LineReader lineReader, PromptProvider promptProvider, Shell shell, - Environment environment) { + public InteractiveShellApplicationRunner(LineReader lineReader, PromptProvider promptProvider, Shell shell) { this.lineReader = lineReader; this.promptProvider = promptProvider; this.shell = shell; - this.environment = environment; } @Override public void run(ApplicationArguments args) throws Exception { - boolean interactive = isEnabled(); - if (interactive) { - InputProvider inputProvider = new JLineInputProvider(lineReader, promptProvider); - shell.run(inputProvider); - } + InputProvider inputProvider = new JLineInputProvider(lineReader, promptProvider); + shell.run(inputProvider); } - public boolean isEnabled() { - return environment.getProperty(SPRING_SHELL_INTERACTIVE_ENABLED,boolean.class, true); - } - - /** - * Helper method to dynamically disable this runner. - */ - public static void disable(ConfigurableEnvironment environment) { - environment.getPropertySources().addFirst(new MapPropertySource("interactive.override", - Collections.singletonMap(SPRING_SHELL_INTERACTIVE_ENABLED, "false"))); + @Override + public boolean canRun(ApplicationArguments args) { + return true; } public static class JLineInputProvider implements InputProvider { @@ -121,5 +96,4 @@ public class InteractiveShellApplicationRunner implements ApplicationRunner { return new ParsedLineInput(lineReader.getParsedLine()); } } - } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellApplicationRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellApplicationRunner.java new file mode 100644 index 00000000..ebb74b87 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/NonInteractiveShellApplicationRunner.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021 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.jline; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.boot.ApplicationArguments; +import org.springframework.core.annotation.Order; +import org.springframework.shell.Input; +import org.springframework.shell.InputProvider; +import org.springframework.shell.Shell; +import org.springframework.shell.ShellRunner; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Non interactive {@link ShellRunner} which is meant to execute shell commands + * without entering interactive shell. + * + * @author Janne Valkealahti + */ +@Order(InteractiveShellApplicationRunner.PRECEDENCE - 50) +public class NonInteractiveShellApplicationRunner implements ShellRunner { + + private final Shell shell; + + public NonInteractiveShellApplicationRunner(Shell shell) { + this.shell = shell; + } + + @Override + public boolean canRun(ApplicationArguments args) { + List argsToShellCommand = Arrays.asList(args.getSourceArgs()); + return !ObjectUtils.isEmpty(argsToShellCommand); + } + + @Override + public void run(ApplicationArguments args) throws Exception { + List argsToShellCommand = Arrays.asList(args.getSourceArgs()); + InputProvider inputProvider = new StringInputProvider(argsToShellCommand); + shell.run(inputProvider); + } + + private class StringInputProvider implements InputProvider { + + private final List commands; + + private boolean done; + + StringInputProvider(List commands) { + this.commands = commands; + } + + @Override + public Input readInput() { + if (!done) { + done = true; + return new Input() { + @Override + public List words() { + return commands; + } + + @Override + public String rawText() { + return StringUtils.collectionToDelimitedString(commands, " "); + } + }; + } + else { + return null; + } + } + } +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellApplicationRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellApplicationRunner.java index 06d017ca..c43168f0 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellApplicationRunner.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellApplicationRunner.java @@ -6,7 +6,7 @@ * 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. @@ -25,25 +25,23 @@ import java.util.stream.Collectors; import org.jline.reader.Parser; import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; import org.springframework.core.annotation.Order; -import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.shell.Shell; +import org.springframework.shell.ShellRunner; +import org.springframework.util.ObjectUtils; /** * Spring Boot ApplicationRunner that looks for process arguments that start with * {@literal @}, which are then interpreted as references to script files to run and exit. - * - *

+ * * Has higher precedence than {@link InteractiveShellApplicationRunner} so that it * prevents it to run if scripts are found. - *

* * @author Eric Bottard */ //tag::documentation[] @Order(InteractiveShellApplicationRunner.PRECEDENCE - 100) // Runs before InteractiveShellApplicationRunner -public class ScriptShellApplicationRunner implements ApplicationRunner { +public class ScriptShellApplicationRunner implements ShellRunner { //end::documentation[] public static final String SPRING_SHELL_SCRIPT = "spring.shell.script"; @@ -59,12 +57,18 @@ public class ScriptShellApplicationRunner implements ApplicationRunner { private final Shell shell; - private final ConfigurableEnvironment environment; - - public ScriptShellApplicationRunner(Parser parser, Shell shell, ConfigurableEnvironment environment) { + public ScriptShellApplicationRunner(Parser parser, Shell shell) { this.parser = parser; this.shell = shell; - this.environment = environment; + } + + @Override + public boolean canRun(ApplicationArguments args) { + List scriptsToRun = args.getNonOptionArgs().stream() + .filter(s -> s.startsWith("@")) + .map(s -> new File(s.substring(1))) + .collect(Collectors.toList()); + return !ObjectUtils.isEmpty(scriptsToRun); } //tag::documentation[] @@ -76,15 +80,10 @@ public class ScriptShellApplicationRunner implements ApplicationRunner { .map(s -> new File(s.substring(1))) .collect(Collectors.toList()); - boolean batchEnabled = environment.getProperty(SPRING_SHELL_SCRIPT_ENABLED, boolean.class, true); - - if (!scriptsToRun.isEmpty() && batchEnabled) { - InteractiveShellApplicationRunner.disable(environment); - for (File file : scriptsToRun) { - try (Reader reader = new FileReader(file); - FileInputProvider inputProvider = new FileInputProvider(reader, parser)) { - shell.run(inputProvider); - } + for (File file : scriptsToRun) { + try (Reader reader = new FileReader(file); + FileInputProvider inputProvider = new FileInputProvider(reader, parser)) { + shell.run(inputProvider); } } } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java index 10f2ce6f..b8af816e 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/result/ThrowableResultHandler.java @@ -60,7 +60,7 @@ public class ThrowableResultHandler extends TerminalAwareResultHandler { - Throwable e = exception; - while (e != null && !(e instanceof ExitRequest)) { - e = e.getCause(); - } - return e == null ? 1 : ((ExitRequest) e).status(); - }; - } -} - -/** - * Example CommandLineRunner that shows how overall shell behavior can be customized. In - * this particular example, any program (process) arguments are assumed to be shell - * commands that need to be executed (and the shell then quits). - */ -@Order(InteractiveShellApplicationRunner.PRECEDENCE - 2) -class ExampleCommandLineRunner implements CommandLineRunner { - - private Shell shell; - - private final ConfigurableEnvironment environment; - - public ExampleCommandLineRunner(Shell shell, ConfigurableEnvironment environment) { - this.shell = shell; - this.environment = environment; - } - - @Override - public void run(String... args) throws Exception { - List commandsToRun = Arrays.stream(args) - .filter(w -> !w.startsWith("@")) - .collect(Collectors.toList()); - if (!commandsToRun.isEmpty()) { - InteractiveShellApplicationRunner.disable(environment); - shell.run(new StringInputProvider(commandsToRun)); - } - } -} - -class StringInputProvider implements InputProvider { - - private final List words; - - private boolean done; - - public StringInputProvider(List words) { - this.words = words; - } - - @Override - public Input readInput() { - if (!done) { - done = true; - return new Input() { - @Override - public List words() { - return words; - } - - @Override - public String rawText() { - return StringUtils.collectionToDelimitedString(words, " "); - } - }; - } - else { - return null; - } - } -} diff --git a/spring-shell-test-samples/src/test/java/com/example/test/integration/CalculatorCommandsIntegrationTest.java b/spring-shell-test-samples/src/test/java/com/example/test/integration/CalculatorCommandsIntegrationTest.java index a84e2cfa..657006c4 100644 --- a/spring-shell-test-samples/src/test/java/com/example/test/integration/CalculatorCommandsIntegrationTest.java +++ b/spring-shell-test-samples/src/test/java/com/example/test/integration/CalculatorCommandsIntegrationTest.java @@ -11,7 +11,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.shell.CommandRegistry; import org.springframework.shell.MethodTarget; import org.springframework.shell.Shell; -import org.springframework.shell.jline.InteractiveShellApplicationRunner; import org.springframework.test.context.ContextConfiguration; import static org.assertj.core.api.Assertions.assertThat; @@ -24,7 +23,7 @@ import static org.springframework.util.ReflectionUtils.findMethod; * * @author Sualeh Fatehi */ -@SpringBootTest(properties = { InteractiveShellApplicationRunner.SPRING_SHELL_INTERACTIVE_ENABLED + "=" + false }) +@SpringBootTest(properties = { "spring.shell.interactive.enabled=false" }) @ContextConfiguration(classes = TestCalculatorStateConfig.class) public class CalculatorCommandsIntegrationTest extends BaseCalculatorTest {