From f90edd42c41c738ed628805922c850c9b05c6730 Mon Sep 17 00:00:00 2001 From: Eric Bottard Date: Sun, 24 Dec 2017 16:49:44 +0100 Subject: [PATCH] Make ResultHandlers configuration more explicit Handle exit as a dedicated case (prevents eg 'exit' commands in scripts to make script quit) Add an example of custom ApplicationRunner Fixes #187 Fixes #183 Decouple ApplicationRunners Make ThrowableResultHandler behave differently in non-interactive mode --- .../java/org/springframework/shell/Shell.java | 8 +- .../shell/SpringShellAutoConfiguration.java | 12 +- ...=> InteractiveShellApplicationRunner.java} | 66 +++++----- .../jline/JLineShellAutoConfiguration.java | 26 ++-- .../jline/ScriptShellApplicationRunner.java | 93 ++++++++++++++ .../AttributedCharSequenceResultHandler.java | 1 - .../shell/result/DefaultResultHandler.java | 7 +- .../result/ExitRequestResultHandler.java | 52 -------- ...meterValidationExceptionResultHandler.java | 1 - .../shell/result/ResultHandlerConfig.java | 20 +++ .../shell/result/ThrowableResultHandler.java | 18 ++- .../result/TypeHierarchyResultHandler.java | 8 +- .../src/main/asciidoc/using-spring-shell.adoc | 16 +-- ...ExampleApplicationRunnerConfiguration.java | 119 ++++++++++++++++++ .../shell/standard/CommandValueProvider.java | 2 - .../StandardAPIAutoConfiguration.java | 3 +- 16 files changed, 324 insertions(+), 128 deletions(-) rename spring-shell-core/src/main/java/org/springframework/shell/jline/{DefaultShellApplicationRunner.java => InteractiveShellApplicationRunner.java} (57%) create mode 100644 spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellApplicationRunner.java delete mode 100644 spring-shell-core/src/main/java/org/springframework/shell/result/ExitRequestResultHandler.java create mode 100644 spring-shell-samples/src/main/java/org/springframework/shell/samples/ExampleApplicationRunnerConfiguration.java diff --git a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java index c35bc617..bbeb3e2a 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/Shell.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/Shell.java @@ -118,7 +118,8 @@ public class Shell implements CommandRegistry { *

*/ public void run(InputProvider inputProvider) throws IOException { - while (true) { + Object result = null; + while (!(result instanceof ExitRequest)) { Input input; try { input = inputProvider.readInput(); @@ -130,8 +131,8 @@ public class Shell implements CommandRegistry { if (input == null) { break; } - Object result = evaluate(input); - if (result != NO_INPUT) { + result = evaluate(input); + if (result != NO_INPUT && !(result instanceof ExitRequest)) { resultHandler.handleResult(result); } } @@ -155,7 +156,6 @@ public class Shell implements CommandRegistry { String command = findLongestCommand(line); List words = input.words(); - Object result; if (command != null) { MethodTarget methodTarget = methodTargets.get(command); Availability availability = methodTarget.getAvailability(); diff --git a/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java b/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java index d80b5804..1f9bbf5f 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/SpringShellAutoConfiguration.java @@ -18,14 +18,15 @@ package org.springframework.shell; import java.util.Collection; +import javax.validation.Validation; +import javax.validation.Validator; + import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; @@ -33,14 +34,11 @@ import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.shell.result.ResultHandlerConfig; -import javax.validation.Validation; -import javax.validation.Validator; - /** * Creates supporting beans for running the Shell */ @Configuration -@ComponentScan(basePackageClasses = ResultHandlerConfig.class) +@Import(ResultHandlerConfig.class) public class SpringShellAutoConfiguration { @Bean diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/DefaultShellApplicationRunner.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellApplicationRunner.java similarity index 57% rename from spring-shell-core/src/main/java/org/springframework/shell/jline/DefaultShellApplicationRunner.java rename to spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellApplicationRunner.java index 404b7c37..c853b855 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/jline/DefaultShellApplicationRunner.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/InteractiveShellApplicationRunner.java @@ -16,11 +16,7 @@ package org.springframework.shell.jline; -import java.io.File; -import java.io.FileReader; -import java.io.Reader; -import java.util.List; -import java.util.stream.Collectors; +import java.util.Collections; import org.jline.reader.LineReader; import org.jline.reader.Parser; @@ -30,28 +26,38 @@ 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; /** - * Default Boot runner that bootstraps the shell application. + * Default Boot runner that bootstraps the shell application in interactive mode. * *

- * Default implementation has default priority and looks for application arguments that start with an {@literal @}, - * assuming they are paths to script files. Executes them and quits if they are present, starts the shell interactively - * otherwise. + * Runs the REPL of the shell unless the {@literal spring.shell.interactive} property has been set to {@literal false}. *

* * @author Eric Bottard */ -//tag::documentation[] -@Order(DefaultShellApplicationRunner.PRECEDENCE) -public class DefaultShellApplicationRunner implements ApplicationRunner { -//end::documentation[] +@Order(InteractiveShellApplicationRunner.PRECEDENCE) +public class InteractiveShellApplicationRunner implements ApplicationRunner { + + /** + * The precedence at which this runner is set. Highger precedence runners may effectively disable this one by setting + * the {@link #SPRING_SHELL_INTERACTIVE_ENABLED} property to {@literal false}. + */ 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; @@ -60,33 +66,37 @@ public class DefaultShellApplicationRunner implements ApplicationRunner { private final Shell shell; - public DefaultShellApplicationRunner(LineReader lineReader, PromptProvider promptProvider, Parser parser, Shell shell) { + private final Environment environment; + + public InteractiveShellApplicationRunner(LineReader lineReader, PromptProvider promptProvider, Parser parser, Shell shell, Environment environment) { this.lineReader = lineReader; this.promptProvider = promptProvider; this.parser = parser; this.shell = shell; + this.environment = environment; } - //tag::documentation[] @Override public void run(ApplicationArguments args) throws Exception { - List scriptsToRun = args.getNonOptionArgs().stream() - .filter(s -> s.startsWith("@")) - .map(s -> new File(s.substring(1))) - .collect(Collectors.toList()); - - if (scriptsToRun.isEmpty()) { + boolean interactive = isEnabled(); + if (interactive) { InputProvider inputProvider = new JLineInputProvider(lineReader, promptProvider); shell.run(inputProvider); - } else { - for (File file : scriptsToRun) { - try (Reader reader = new FileReader(file); FileInputProvider inputProvider = new FileInputProvider(reader, parser)) { - shell.run(inputProvider); - } - } } } - //end::documentation[] + + 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"))); + } + public static class JLineInputProvider implements InputProvider { private final LineReader lineReader; diff --git a/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java b/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java index 2617ac55..3afc5ac5 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/JLineShellAutoConfiguration.java @@ -16,6 +16,9 @@ package org.springframework.shell.jline; +import static org.springframework.shell.jline.InteractiveShellApplicationRunner.SPRING_SHELL_INTERACTIVE; +import static org.springframework.shell.jline.ScriptShellApplicationRunner.SPRING_SHELL_SCRIPT; + import java.io.*; import java.nio.file.Paths; import java.util.List; @@ -33,25 +36,20 @@ import org.jline.utils.AttributedStyle; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.context.ApplicationListener; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.EventListener; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; import org.springframework.shell.CompletingParsedLine; import org.springframework.shell.CompletionContext; import org.springframework.shell.CompletionProposal; -import org.springframework.shell.ExitRequest; -import org.springframework.shell.Input; -import org.springframework.shell.InputProvider; -import org.springframework.shell.ResultHandler; import org.springframework.shell.Shell; /** @@ -83,9 +81,15 @@ public class JLineShellAutoConfiguration { } @Bean - @ConditionalOnMissingBean(ApplicationRunner.class) - public ApplicationRunner applicationRunner(Parser parser) { - return new DefaultShellApplicationRunner(lineReader(), promptProvider, parser, shell); + @ConditionalOnProperty(prefix = SPRING_SHELL_INTERACTIVE, value = InteractiveShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true) + public ApplicationRunner interactiveApplicationRunner(Parser parser, Environment environment) { + return new InteractiveShellApplicationRunner(lineReader(), promptProvider, parser, shell, environment); + } + + @Bean + @ConditionalOnProperty(prefix = SPRING_SHELL_SCRIPT, value = ScriptShellApplicationRunner.ENABLED, havingValue = "true", matchIfMissing = true) + public ApplicationRunner scriptApplicationRunner(Parser parser, ConfigurableEnvironment environment) { + return new ScriptShellApplicationRunner(parser, shell, environment); } 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 new file mode 100644 index 00000000..68f0929f --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellApplicationRunner.java @@ -0,0 +1,93 @@ +/* + * Copyright 2018 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 + * + * http://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.io.File; +import java.io.FileReader; +import java.io.Reader; +import java.util.List; +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; + +/** + * 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 { +//end::documentation[] + + public static final String SPRING_SHELL_SCRIPT = "spring.shell.script"; + public static final String ENABLED = "spring.shell.script"; + + /** + * The name of the environment property that allows to disable the behavior of this + * runner. + */ + public static final String SPRING_SHELL_SCRIPT_ENABLED = SPRING_SHELL_SCRIPT + "." + ENABLED; + + private final Parser parser; + + private final Shell shell; + + private final ConfigurableEnvironment environment; + + public ScriptShellApplicationRunner(Parser parser, Shell shell, ConfigurableEnvironment environment) { + this.parser = parser; + this.shell = shell; + this.environment = environment; + } + + //tag::documentation[] + + @Override + public void run(ApplicationArguments args) throws Exception { + List scriptsToRun = args.getNonOptionArgs().stream() + .filter(s -> s.startsWith("@")) + .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); + } + } + } + } + //end::documentation[] + +} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/AttributedCharSequenceResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/AttributedCharSequenceResultHandler.java index f7df75ff..f1b3ed3a 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/result/AttributedCharSequenceResultHandler.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/result/AttributedCharSequenceResultHandler.java @@ -26,7 +26,6 @@ import org.springframework.stereotype.Component; * * @author Eric Bottard */ -@Component public class AttributedCharSequenceResultHandler extends TerminalAwareResultHandler { @Override diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/DefaultResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/DefaultResultHandler.java index 4a0c2a9c..a75ee58d 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/result/DefaultResultHandler.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/result/DefaultResultHandler.java @@ -25,11 +25,10 @@ import org.springframework.stereotype.Component; * * @author Eric Bottard */ -@Component -public class DefaultResultHandler implements ResultHandler { +public class DefaultResultHandler extends TerminalAwareResultHandler { @Override - public void handleResult(Object result) { - System.out.println(String.valueOf(result)); + protected void doHandleResult(Object result) { + terminal.writer().println(String.valueOf(result)); } } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/ExitRequestResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/ExitRequestResultHandler.java deleted file mode 100644 index 0e16fe5e..00000000 --- a/spring-shell-core/src/main/java/org/springframework/shell/result/ExitRequestResultHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2017 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 - * - * http://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.result; - -import java.io.Closeable; -import java.io.IOException; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.shell.ExitRequest; -import org.springframework.shell.ResultHandler; -import org.springframework.stereotype.Component; - -/** - * Intercepts {@link org.springframework.shell.ExitRequest} exceptions and gracefully exits the running process. - * - * @author Eric Bottard - */ -@Component -public class ExitRequestResultHandler implements ResultHandler { - - @Autowired - private ApplicationContext applicationContext; - - @Override - public void handleResult(ExitRequest result) { - if (applicationContext instanceof Closeable) { - try { - ((Closeable) applicationContext).close(); - } - catch (IOException e) { - // ignore - } - System.exit(result.status()); - } - - } -} diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/ParameterValidationExceptionResultHandler.java b/spring-shell-core/src/main/java/org/springframework/shell/result/ParameterValidationExceptionResultHandler.java index d913170b..30ba9d34 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/result/ParameterValidationExceptionResultHandler.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/result/ParameterValidationExceptionResultHandler.java @@ -40,7 +40,6 @@ import java.util.stream.StreamSupport; * * @author Eric Bottard */ -@Component public class ParameterValidationExceptionResultHandler extends TerminalAwareResultHandler { diff --git a/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java b/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java index 86b6b8eb..f38dec72 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/result/ResultHandlerConfig.java @@ -55,4 +55,24 @@ public class ResultHandlerConfig { return new TerminalSizeAwareResultHandler(); } + @Bean + public AttributedCharSequenceResultHandler attributedCharSequenceResultHandler() { + return new AttributedCharSequenceResultHandler(); + } + + @Bean + public DefaultResultHandler defaultResultHandler() { + return new DefaultResultHandler(); + } + + @Bean + public ParameterValidationExceptionResultHandler parameterValidationExceptionResultHandler() { + return new ParameterValidationExceptionResultHandler(); + } + + @Bean + public ThrowableResultHandler throwableResultHandler() { + return new ThrowableResultHandler(); + } + } 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 ff19902a..d5e495d0 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 @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.shell.CommandRegistry; import org.springframework.shell.ResultHandler; +import org.springframework.shell.jline.InteractiveShellApplicationRunner; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -34,7 +35,6 @@ import org.springframework.util.StringUtils; * * @author Eric Bottard */ -@Component public class ThrowableResultHandler extends TerminalAwareResultHandler { /** @@ -47,13 +47,16 @@ public class ThrowableResultHandler extends TerminalAwareResultHandler { } Class clazz = result.getClass(); ResultHandler handler = getResultHandler(clazz); - try { - handler.handleResult(result); - } - catch (Exception e) { - // Protect against exceptions happening in ResultHandlers - getResultHandler(e.getClass()).handleResult(e); - } + handler.handleResult(result); } private ResultHandler getResultHandler(Class clazz) { diff --git a/spring-shell-docs/src/main/asciidoc/using-spring-shell.adoc b/spring-shell-docs/src/main/asciidoc/using-spring-shell.adoc index fe8593bd..892134af 100644 --- a/spring-shell-docs/src/main/asciidoc/using-spring-shell.adoc +++ b/spring-shell-docs/src/main/asciidoc/using-spring-shell.adoc @@ -864,20 +864,20 @@ public class CustomPromptProvider implements PromptProvider { ---- ==== Customizing Command Line Options Behavior -Spring Shell comes with a default Spring Boot `ApplicationRunner` -that bootstraps the Shell REPL. It sets up the JLine infrastructure and eventually -calls `Shell.run()`. +Spring Shell comes with two default Spring Boot `ApplicationRunners`: -If the application is started with arguments that start with `@` though, it assumes those -are local file names and tries to run commands contained in those files (with the same -semantics as the xref:script-command[script command]) and then exits the process. +* `InteractiveShellApplicationRunner` bootstraps the Shell REPL. It sets up the JLine infrastructure and eventually +calls `Shell.run()` +* `ScriptShellApplicationRunner` looks for program arguments that start with `@`, assumes those are local file names and +tries to run commands contained in those files (with the same semantics as the xref:script-command[script command]) and +then exits the process (by effectively disabling the `InteractiveShellApplicationRunner`, see below). If this behavior does not suit you, simply provide one (or more) bean of type `ApplicationRunner` -and it will replace the default. You'll want to take inspiration from the `DefaultShellApplicationRunner`: +and optionally disable the standard ones. You'll want to take inspiration from the `ScriptShellApplicationRunner`: [source,java] ---- -include::../../../../spring-shell-core/src/main/java/org/springframework/shell/jline/DefaultShellApplicationRunner.java[tag=documentation] +include::../../../../spring-shell-core/src/main/java/org/springframework/shell/jline/ScriptShellApplicationRunner.java[tag=documentation] ... ---- diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/ExampleApplicationRunnerConfiguration.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/ExampleApplicationRunnerConfiguration.java new file mode 100644 index 00000000..3cdfcafd --- /dev/null +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/ExampleApplicationRunnerConfiguration.java @@ -0,0 +1,119 @@ +/* + * Copyright 2017 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 + * + * http://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.samples; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.ExitCodeExceptionMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.shell.ExitRequest; +import org.springframework.shell.Input; +import org.springframework.shell.InputProvider; +import org.springframework.shell.Shell; +import org.springframework.shell.jline.InteractiveShellApplicationRunner; +import org.springframework.util.StringUtils; + +@Configuration +public class ExampleApplicationRunnerConfiguration { + + @Autowired + private Shell shell; + + @Bean + public CommandLineRunner exampleCommandLineRunner(ConfigurableEnvironment environment) { + return new ExampleCommandLineRunner(shell, environment); + } + + @Bean + public ExitCodeExceptionMapper exitCodeExceptionMapper() { + return exception -> { + 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-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java index 6338b146..b23915b6 100644 --- a/spring-shell-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/CommandValueProvider.java @@ -36,8 +36,6 @@ public class CommandValueProvider extends ValueProviderSupport { private final CommandRegistry commandRegistry; - @Lazy - @Autowired public CommandValueProvider(CommandRegistry commandRegistry) { this.commandRegistry = commandRegistry; } diff --git a/spring-shell-standard/src/main/java/org/springframework/shell/standard/StandardAPIAutoConfiguration.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/StandardAPIAutoConfiguration.java index 8de139d5..8028a3d5 100644 --- a/spring-shell-standard/src/main/java/org/springframework/shell/standard/StandardAPIAutoConfiguration.java +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/StandardAPIAutoConfiguration.java @@ -19,6 +19,7 @@ package org.springframework.shell.standard; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; import org.springframework.core.convert.ConversionService; import org.springframework.shell.CommandRegistry; import org.springframework.shell.MethodTargetRegistrar; @@ -33,7 +34,7 @@ import org.springframework.shell.ParameterResolver; public class StandardAPIAutoConfiguration { @Bean - public ValueProvider commandValueProvider(CommandRegistry commandRegistry) { + public ValueProvider commandValueProvider(@Lazy CommandRegistry commandRegistry) { return new CommandValueProvider(commandRegistry); }