Rework bean handling

- Lot of rework to move better model to work around bean cycles
- Remove use of @Lazy
- Move StandardAPIAutoConfiguration to autoconfig package
- Remove some of a direct ObjectProvider use in constructors
- Adds spring-native support with most of a things working out of a box
- Relates #324
- Relates #329
- Relates #323
This commit is contained in:
Janne Valkealahti
2021-12-24 08:50:40 +00:00
parent 2706da37cc
commit f6394a4531
33 changed files with 342 additions and 168 deletions

View File

@@ -18,8 +18,6 @@ package org.springframework.shell.boot;
import org.jline.reader.LineReader;
import org.jline.reader.Parser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -33,27 +31,30 @@ 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;
@Configuration
@Configuration(proxyBeanMethods = false)
public class ApplicationRunnerAutoConfiguration {
@Autowired
private Shell shell;
@Autowired
private PromptProvider promptProvider;
@Autowired
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 ApplicationRunner interactiveApplicationRunner(Environment environment) {
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 ApplicationRunner scriptApplicationRunner(Parser parser, ConfigurableEnvironment environment) {
public ScriptShellApplicationRunner scriptApplicationRunner(Parser parser, ConfigurableEnvironment environment) {
return new ScriptShellApplicationRunner(parser, shell, environment);
}
}

View File

@@ -27,9 +27,9 @@ public class CommandRegistryAutoConfiguration {
@Bean
public CommandRegistry commandRegistry(
ObjectProvider<MethodTargetRegistrar> methodTargerRegistrars) {
ObjectProvider<MethodTargetRegistrar> methodTargetRegistrars) {
ConfigurableCommandRegistry registry = new ConfigurableCommandRegistry();
methodTargerRegistrars.orderedStream().forEach(resolver -> {
methodTargetRegistrars.orderedStream().forEach(resolver -> {
resolver.register(registry);
});
return registry;

View File

@@ -31,7 +31,7 @@ import org.springframework.shell.CompletionContext;
import org.springframework.shell.CompletionProposal;
import org.springframework.shell.Shell;
@Configuration
@Configuration(proxyBeanMethods = false)
public class CompleterAutoConfiguration {
@Autowired

View File

@@ -28,7 +28,7 @@ import org.springframework.context.annotation.Bean;
*
* @author Eric Bottard
*/
@Configuration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ JCommander.class, JCommanderParameterResolver.class })
public class JCommanderParameterResolverAutoConfiguration {

View File

@@ -22,7 +22,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Configuration(proxyBeanMethods = false)
public class JLineAutoConfiguration {
@Configuration

View File

@@ -37,7 +37,7 @@ import org.springframework.shell.jline.PromptProvider;
* @author Eric Bottard
* @author Florent Biville
*/
@Configuration
@Configuration(proxyBeanMethods = false)
public class JLineShellAutoConfiguration {
@Bean(destroyMethod = "close")

View File

@@ -29,7 +29,6 @@ import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -37,32 +36,36 @@ import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.shell.CommandRegistry;
@Configuration
@Configuration(proxyBeanMethods = false)
public class LineReaderAutoConfiguration {
@Autowired
private Terminal terminal;
@Autowired
private Completer completer;
@Autowired
private Parser parser;
@Autowired
private CommandRegistry commandRegistry;
@Autowired
private org.jline.reader.History jLineHistory;
@Value("${spring.application.name:spring-shell}.log")
private String historyPath;
public LineReaderAutoConfiguration(Terminal terminal, Completer completer, Parser parser,
CommandRegistry commandRegistry, org.jline.reader.History jLineHistory) {
this.terminal = terminal;
this.completer = completer;
this.parser = parser;
this.commandRegistry = commandRegistry;
this.jLineHistory = jLineHistory;
}
@EventListener
public void onContextClosedEvent(ContextClosedEvent event) throws IOException {
jLineHistory.save();
}
@Value("${spring.application.name:spring-shell}.log")
private String historyPath;
@Bean
public LineReader lineReader() {
LineReaderBuilder lineReaderBuilder = LineReaderBuilder.builder()

View File

@@ -0,0 +1,25 @@
package org.springframework.shell.boot;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.shell.ParameterResolver;
import org.springframework.shell.standard.StandardParameterResolver;
import org.springframework.shell.standard.ValueProvider;
@Configuration(proxyBeanMethods = false)
public class ParameterResolverAutoConfiguration {
@Bean
public ParameterResolver standardParameterResolver(@Qualifier("spring-shell") ConversionService conversionService,
ObjectProvider<ValueProvider> valueProviders) {
Set<ValueProvider> collect = valueProviders.orderedStream().collect(Collectors.toSet());
return new StandardParameterResolver(conversionService, collect);
}
}

View File

@@ -19,11 +19,7 @@ package org.springframework.shell.boot;
import java.util.Collection;
import java.util.Set;
import javax.validation.Validation;
import javax.validation.Validator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -33,6 +29,7 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.shell.CommandRegistry;
import org.springframework.shell.ResultHandler;
import org.springframework.shell.ResultHandlerService;
import org.springframework.shell.Shell;
@@ -42,7 +39,7 @@ import org.springframework.shell.result.ResultHandlerConfig;
/**
* Creates supporting beans for running the Shell
*/
@Configuration
@Configuration(proxyBeanMethods = false)
@Import(ResultHandlerConfig.class)
public class SpringShellAutoConfiguration {
@@ -66,12 +63,6 @@ public class SpringShellAutoConfiguration {
return defaultConversionService;
}
@Bean
@ConditionalOnMissingBean(Validator.class)
public Validator validator() {
return Validation.buildDefaultValidatorFactory().getValidator();
}
@Bean
public ResultHandlerService resultHandlerService(Set<ResultHandler<?>> resultHandlers) {
GenericResultHandlerService service = new GenericResultHandlerService();
@@ -82,7 +73,7 @@ public class SpringShellAutoConfiguration {
}
@Bean
public Shell shell(ResultHandlerService resultHandlerService) {
return new Shell(resultHandlerService);
public Shell shell(ResultHandlerService resultHandlerService, CommandRegistry commandRegistry) {
return new Shell(resultHandlerService, commandRegistry);
}
}

View File

@@ -14,27 +14,28 @@
* limitations under the License.
*/
package org.springframework.shell.standard;
package org.springframework.shell.boot;
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;
import org.springframework.shell.ParameterResolver;
import org.springframework.shell.standard.CommandValueProvider;
import org.springframework.shell.standard.EnumValueProvider;
import org.springframework.shell.standard.FileValueProvider;
import org.springframework.shell.standard.StandardMethodTargetRegistrar;
import org.springframework.shell.standard.ValueProvider;
/**
* Sets up all required beans for supporting the standard Shell API.
*
* @author Eric Bottard
*/
@Configuration
@Configuration(proxyBeanMethods = false)
public class StandardAPIAutoConfiguration {
@Bean
public ValueProvider commandValueProvider(@Lazy CommandRegistry commandRegistry) {
public ValueProvider commandValueProvider(CommandRegistry commandRegistry) {
return new CommandValueProvider(commandRegistry);
}
@@ -52,9 +53,4 @@ public class StandardAPIAutoConfiguration {
public MethodTargetRegistrar standardMethodTargetResolver() {
return new StandardMethodTargetRegistrar();
}
@Bean
public ParameterResolver standardParameterResolver(@Qualifier("spring-shell") ConversionService conversionService) {
return new StandardParameterResolver(conversionService);
}
}

View File

@@ -16,8 +16,6 @@
package org.springframework.shell.boot;
import java.util.List;
import org.jline.reader.Parser;
import org.springframework.beans.factory.ObjectProvider;
@@ -26,8 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.shell.ParameterResolver;
import org.springframework.shell.Shell;
import org.springframework.shell.result.ThrowableResultHandler;
import org.springframework.shell.standard.commands.Clear;
import org.springframework.shell.standard.commands.Help;
import org.springframework.shell.standard.commands.History;
@@ -40,15 +37,15 @@ import org.springframework.shell.standard.commands.Stacktrace;
*
* @author Eric Bottard
*/
@Configuration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Help.Command.class })
public class StandardCommandsAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Help.Command.class)
@ConditionalOnProperty(prefix = "spring.shell.command.help", value = "enabled", havingValue = "true", matchIfMissing = true)
public Help help(List<ParameterResolver> parameterResolvers) {
return new Help(parameterResolvers);
public Help help() {
return new Help();
}
@Bean
@@ -68,15 +65,15 @@ public class StandardCommandsAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Stacktrace.Command.class)
@ConditionalOnProperty(prefix = "spring.shell.command.stacktrace", value = "enabled", havingValue = "true", matchIfMissing = true)
public Stacktrace stacktrace() {
return new Stacktrace();
public Stacktrace stacktrace(ObjectProvider<ThrowableResultHandler> throwableResultHandler) {
return new Stacktrace(throwableResultHandler);
}
@Bean
@ConditionalOnMissingBean(Script.Command.class)
@ConditionalOnProperty(prefix = "spring.shell.command.script", value = "enabled", havingValue = "true", matchIfMissing = true)
public Script script(ObjectProvider<Shell> shell, Parser parser) {
return new Script(shell, parser);
public Script script(Parser parser) {
return new Script(parser);
}
@Bean

View File

@@ -7,4 +7,6 @@ org.springframework.shell.boot.CompleterAutoConfiguration,\
org.springframework.shell.boot.JLineAutoConfiguration,\
org.springframework.shell.boot.JLineShellAutoConfiguration,\
org.springframework.shell.boot.JCommanderParameterResolverAutoConfiguration,\
org.springframework.shell.boot.ParameterResolverAutoConfiguration,\
org.springframework.shell.boot.StandardAPIAutoConfiguration,\
org.springframework.shell.boot.StandardCommandsAutoConfiguration

View File

@@ -30,7 +30,6 @@ import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
@@ -70,10 +69,9 @@ public class Shell {
@Autowired
protected ApplicationContext applicationContext;
@Autowired
private CommandRegistry commandRegistry;
private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
private Validator validator = Utils.defaultValidator();
protected Map<String, MethodTarget> methodTargets = new HashMap<>();
@@ -85,8 +83,9 @@ public class Shell {
*/
protected static final Object UNRESOLVED = new Object();
public Shell(ResultHandlerService resultHandlerService) {
public Shell(ResultHandlerService resultHandlerService, CommandRegistry commandRegistry) {
this.resultHandlerService = resultHandlerService;
this.commandRegistry = commandRegistry;
}
@Autowired(required = false)

View File

@@ -25,6 +25,10 @@ import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
@@ -100,4 +104,30 @@ public class Utils {
.collect(Collectors.toList());
return words;
}
private final static ValidatorFactory DEFAULT_VALIDATOR_FACTORY;
private final static Validator DEFAULT_VALIDATOR;
static {
DEFAULT_VALIDATOR_FACTORY = Validation.buildDefaultValidatorFactory();
DEFAULT_VALIDATOR = DEFAULT_VALIDATOR_FACTORY.getValidator();
}
/**
* Gets a default shared validation factory.
*
* @return default validation factory
*/
public static ValidatorFactory defaultValidatorFactory() {
return DEFAULT_VALIDATOR_FACTORY;
}
/**
* Gets a default shared validator.
*
* @return default validator
*/
public static Validator defaultValidator() {
return DEFAULT_VALIDATOR;
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.shell.result;
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedCharSequence;
import org.springframework.shell.ResultHandler;
@@ -27,6 +28,10 @@ import org.springframework.shell.ResultHandler;
*/
public class AttributedCharSequenceResultHandler extends TerminalAwareResultHandler<AttributedCharSequence> {
public AttributedCharSequenceResultHandler(Terminal terminal) {
super(terminal);
}
@Override
protected void doHandleResult(AttributedCharSequence result) {
terminal.writer().println(result.toAnsi(terminal));

View File

@@ -16,6 +16,8 @@
package org.springframework.shell.result;
import org.jline.terminal.Terminal;
import org.springframework.shell.ResultHandler;
/**
@@ -26,6 +28,10 @@ import org.springframework.shell.ResultHandler;
*/
public class DefaultResultHandler extends TerminalAwareResultHandler<Object> {
public DefaultResultHandler(Terminal terminal) {
super(terminal);
}
@Override
protected void doHandleResult(Object result) {
terminal.writer().println(String.valueOf(result));

View File

@@ -24,6 +24,7 @@ import java.util.stream.StreamSupport;
import javax.validation.ElementKind;
import javax.validation.Path;
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
@@ -43,6 +44,10 @@ import org.springframework.shell.Utils;
public class ParameterValidationExceptionResultHandler
extends TerminalAwareResultHandler<ParameterValidationException> {
public ParameterValidationExceptionResultHandler(Terminal terminal) {
super(terminal);
}
@Autowired
private List<ParameterResolver> parameterResolvers;

View File

@@ -16,10 +16,15 @@
package org.springframework.shell.result;
import org.jline.terminal.Terminal;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.shell.CommandRegistry;
import org.springframework.shell.TerminalSizeAware;
import org.springframework.shell.jline.InteractiveShellApplicationRunner;
/**
* Used for explicit configuration of {@link org.springframework.shell.ResultHandler}s.
@@ -32,28 +37,29 @@ public class ResultHandlerConfig {
@Bean
@ConditionalOnClass(TerminalSizeAware.class)
public TerminalSizeAwareResultHandler terminalSizeAwareResultHandler() {
return new TerminalSizeAwareResultHandler();
public TerminalSizeAwareResultHandler terminalSizeAwareResultHandler(Terminal terminal) {
return new TerminalSizeAwareResultHandler(terminal);
}
@Bean
public AttributedCharSequenceResultHandler attributedCharSequenceResultHandler() {
return new AttributedCharSequenceResultHandler();
public AttributedCharSequenceResultHandler attributedCharSequenceResultHandler(Terminal terminal) {
return new AttributedCharSequenceResultHandler(terminal);
}
@Bean
public DefaultResultHandler defaultResultHandler() {
return new DefaultResultHandler();
public DefaultResultHandler defaultResultHandler(Terminal terminal) {
return new DefaultResultHandler(terminal);
}
@Bean
public ParameterValidationExceptionResultHandler parameterValidationExceptionResultHandler() {
return new ParameterValidationExceptionResultHandler();
public ParameterValidationExceptionResultHandler parameterValidationExceptionResultHandler(Terminal terminal) {
return new ParameterValidationExceptionResultHandler(terminal);
}
@Bean
public ThrowableResultHandler throwableResultHandler() {
return new ThrowableResultHandler();
public ThrowableResultHandler throwableResultHandler(Terminal terminal, CommandRegistry commandRegistry,
ObjectProvider<InteractiveShellApplicationRunner> interactiveApplicationRunner) {
return new ThrowableResultHandler(terminal, commandRegistry, interactiveApplicationRunner);
}
}

View File

@@ -18,8 +18,6 @@ package org.springframework.shell.result;
import org.jline.terminal.Terminal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.shell.ResultHandler;
/**
@@ -28,10 +26,10 @@ import org.springframework.shell.ResultHandler;
* @author Eric Bottard
*/
public abstract class TerminalAwareResultHandler<T> implements ResultHandler<T> {
protected Terminal terminal;
@Autowired @Lazy
public void setTerminal(Terminal terminal) {
protected TerminalAwareResultHandler(Terminal terminal) {
this.terminal = terminal;
}

View File

@@ -16,6 +16,8 @@
package org.springframework.shell.result;
import org.jline.terminal.Terminal;
import org.springframework.shell.TerminalSizeAware;
/**
@@ -25,6 +27,9 @@ import org.springframework.shell.TerminalSizeAware;
*/
public class TerminalSizeAwareResultHandler extends TerminalAwareResultHandler<TerminalSizeAware> {
public TerminalSizeAwareResultHandler(Terminal terminal) {
super(terminal);
}
@Override
protected void doHandleResult(TerminalSizeAware result) {

View File

@@ -16,12 +16,12 @@
package org.springframework.shell.result;
import org.jline.terminal.Terminal;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.shell.CommandRegistry;
import org.springframework.shell.ResultHandler;
import org.springframework.shell.jline.InteractiveShellApplicationRunner;
@@ -43,11 +43,16 @@ public class ThrowableResultHandler extends TerminalAwareResultHandler<Throwable
private Throwable lastError;
@Autowired @Lazy
private CommandRegistry commandRegistry;
@Autowired @Lazy
private InteractiveShellApplicationRunner interactiveRunner;
private ObjectProvider<InteractiveShellApplicationRunner> interactiveRunner;
public ThrowableResultHandler(Terminal terminal, CommandRegistry commandRegistry,
ObjectProvider<InteractiveShellApplicationRunner> interactiveRunner) {
super(terminal);
this.commandRegistry = commandRegistry;
this.interactiveRunner = interactiveRunner;
}
@Override
protected void doHandleResult(Throwable result) {
@@ -55,7 +60,7 @@ public class ThrowableResultHandler extends TerminalAwareResultHandler<Throwable
String toPrint = StringUtils.hasLength(result.getMessage()) ? result.getMessage() : result.toString();
terminal.writer().println(new AttributedString(toPrint,
AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)).toAnsi());
if (interactiveRunner.isEnabled() && commandRegistry.listCommands().containsKey(DETAILS_COMMAND_NAME)) {
if (interactiveRunner.getIfAvailable().isEnabled() && commandRegistry.listCommands().containsKey(DETAILS_COMMAND_NAME)) {
terminal.writer().println(
new AttributedStringBuilder()
.append("Details of the error have been omitted. You can use the ", AttributedStyle.DEFAULT.foreground(AttributedStyle.RED))
@@ -65,7 +70,7 @@ public class ThrowableResultHandler extends TerminalAwareResultHandler<Throwable
);
}
terminal.writer().flush();
if (!interactiveRunner.isEnabled()) {
if (!interactiveRunner.getIfAvailable().isEnabled()) {
if (result instanceof RuntimeException) {
throw (RuntimeException) result;
}

View File

@@ -25,7 +25,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.metadata.BeanDescriptor;
@@ -43,6 +42,7 @@ import org.springframework.shell.CompletionContext;
import org.springframework.shell.CompletionProposal;
import org.springframework.shell.ParameterDescription;
import org.springframework.shell.ParameterResolver;
import org.springframework.shell.Utils;
import org.springframework.shell.ValueResult;
import org.springframework.util.ReflectionUtils;
@@ -59,7 +59,7 @@ public class JCommanderParameterResolver implements ParameterResolver {
private static final Collection<Class<? extends Annotation>> JCOMMANDER_ANNOTATIONS = Arrays.asList(Parameter.class,
DynamicParameter.class, ParametersDelegate.class);
private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
private Validator validator = Utils.defaultValidator();
@Autowired(required = false)
public void setValidatorFactory(ValidatorFactory validatorFactory) {

View File

@@ -22,13 +22,13 @@ import org.springframework.context.annotation.Import;
import org.springframework.shell.boot.JCommanderParameterResolverAutoConfiguration;
import org.springframework.shell.boot.JLineShellAutoConfiguration;
import org.springframework.shell.boot.SpringShellAutoConfiguration;
import org.springframework.shell.boot.StandardAPIAutoConfiguration;
import org.springframework.shell.boot.StandardCommandsAutoConfiguration;
import org.springframework.shell.samples.jcommander.JCommanderCommands;
import org.springframework.shell.samples.standard.Commands;
import org.springframework.shell.samples.standard.DynamicCommands;
import org.springframework.shell.samples.standard.TableCommands;
import org.springframework.shell.standard.FileValueProvider;
import org.springframework.shell.standard.StandardAPIAutoConfiguration;
/**
* This class shows how to use the full extent of Spring Shell without relying on Boot auto configuration.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-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.
@@ -16,11 +16,9 @@
package org.springframework.shell.standard.commands;
import org.jline.terminal.Terminal;
import org.jline.utils.InfoCmp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.shell.standard.AbstractShellComponent;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
@@ -28,9 +26,10 @@ import org.springframework.shell.standard.ShellMethod;
* ANSI console related commands.
*
* @author Eric Bottard
* @author Janne Valkealahti
*/
@ShellComponent
public class Clear {
public class Clear extends AbstractShellComponent {
/**
* Marker interface for beans providing {@literal clear} functionality to the shell.
@@ -45,11 +44,11 @@ public class Clear {
*/
public interface Command {}
@Autowired @Lazy
private Terminal terminal;
public Clear() {
}
@ShellMethod("Clear the shell screen.")
public void clear() {
terminal.puts(InfoCmp.Capability.clear_screen);
getTerminal().puts(InfoCmp.Capability.clear_screen);
}
}

View File

@@ -29,21 +29,18 @@ import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.validation.MessageInterpolator;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import javax.validation.metadata.ConstraintDescriptor;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.shell.Availability;
import org.springframework.shell.CommandRegistry;
import org.springframework.shell.MethodTarget;
import org.springframework.shell.ParameterDescription;
import org.springframework.shell.ParameterResolver;
import org.springframework.shell.Utils;
import org.springframework.shell.standard.AbstractShellComponent;
import org.springframework.shell.standard.CommandValueProvider;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
@@ -60,7 +57,7 @@ import static java.util.stream.Collectors.toMap;
* @author Eric Bottard
*/
@ShellComponent
public class Help {
public class Help extends AbstractShellComponent {
/**
* Marker interface for beans providing {@literal help} functionality to the shell.
@@ -80,21 +77,9 @@ public class Help {
public interface Command {
}
private final List<ParameterResolver> parameterResolvers;
private MessageInterpolator messageInterpolator = Utils.defaultValidatorFactory().getMessageInterpolator();
private ObjectProvider<CommandRegistry> commandRegistry;
private MessageInterpolator messageInterpolator = Validation.buildDefaultValidatorFactory()
.getMessageInterpolator();
@Autowired
public Help(List<ParameterResolver> parameterResolvers) {
this.parameterResolvers = parameterResolvers;
}
@Autowired // ctor injection impossible b/c of circular dependency
public void setCommandRegistry(ObjectProvider<CommandRegistry> commandRegistry) {
this.commandRegistry = commandRegistry;
public Help() {
}
@Autowired(required = false)
@@ -102,7 +87,6 @@ public class Help {
this.messageInterpolator = validatorFactory.getMessageInterpolator();
}
@ShellMethod(value = "Display help about available commands.", prefix = "-")
public CharSequence help(
@ShellOption(defaultValue = ShellOption.NULL, valueProvider = CommandValueProvider.class, value = { "-C",
@@ -121,7 +105,7 @@ public class Help {
* Return a description of a specific command. Uses a layout inspired by *nix man pages.
*/
private CharSequence documentCommand(String command) {
MethodTarget methodTarget = commandRegistry.getIfAvailable().listCommands().get(command);
MethodTarget methodTarget = getCommandRegistry().listCommands().get(command);
if (methodTarget == null) {
throw new IllegalArgumentException("Unknown command '" + command + "'");
}
@@ -248,7 +232,7 @@ public class Help {
}
private void documentAliases(AttributedStringBuilder result, String command, MethodTarget methodTarget) {
Set<String> aliases = commandRegistry.getIfAvailable().listCommands().entrySet().stream()
Set<String> aliases = getCommandRegistry().listCommands().entrySet().stream()
.filter(e -> e.getValue().equals(methodTarget))
.map(Map.Entry::getKey)
.filter(c -> !command.equals(c))
@@ -277,7 +261,7 @@ public class Help {
}
private CharSequence listCommands() {
Map<String, MethodTarget> commandsByName = commandRegistry.getIfAvailable().listCommands();
Map<String, MethodTarget> commandsByName = getCommandRegistry().listCommands();
SortedMap<String, Map<String, MethodTarget>> commandsByGroupAndName = commandsByName.entrySet().stream()
.collect(groupingBy(e -> e.getValue().getGroup(), TreeMap::new, // group by and sort by command group
@@ -335,7 +319,7 @@ public class Help {
private List<ParameterDescription> getParameterDescriptions(MethodTarget methodTarget) {
return Utils.createMethodParameters(methodTarget.getMethod())
.flatMap(mp -> parameterResolvers.stream().filter(pr -> pr.supports(mp)).limit(1L)
.flatMap(mp -> getParameterResolver().filter(pr -> pr.supports(mp)).limit(1L)
.flatMap(pr -> pr.describe(mp)))
.collect(Collectors.toList());

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2017-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.standard.commands;
import java.io.File;
@@ -7,9 +22,8 @@ import java.io.Reader;
import org.jline.reader.Parser;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.shell.Shell;
import org.springframework.shell.jline.FileInputProvider;
import org.springframework.shell.standard.AbstractShellComponent;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
@@ -17,16 +31,14 @@ import org.springframework.shell.standard.ShellMethod;
* A command that can read and execute other commands from a file.
*
* @author Eric Bottard
* @author Janne Valkealahti
*/
@ShellComponent
public class Script {
private final ObjectProvider<Shell> shell;
public class Script extends AbstractShellComponent {
private final Parser parser;
public Script(ObjectProvider<Shell> shell, Parser parser) {
this.shell = shell;
public Script(Parser parser) {
this.parser = parser;
}
@@ -48,7 +60,7 @@ public class Script {
public void script(File file) throws IOException {
Reader reader = new FileReader(file);
try (FileInputProvider inputProvider = new FileInputProvider(reader, parser)) {
shell.getIfAvailable().run(inputProvider);
getShell().run(inputProvider);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2017-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.
@@ -13,22 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.shell.standard.commands;
import org.jline.terminal.Terminal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.shell.result.ThrowableResultHandler;
import org.springframework.shell.standard.AbstractShellComponent;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
/**
* A command to display the full stacktrace when an error occurs.
*
* @author Eric Bottard
* @author Janne Valkealahti
*/
@ShellComponent
public class Stacktrace {
public class Stacktrace extends AbstractShellComponent {
/**
* Marker interface for beans providing {@literal stacktrace} functionality to the shell.
@@ -43,17 +43,16 @@ public class Stacktrace {
*/
public interface Command {}
@Autowired @Lazy
private Terminal terminal;
@Autowired
private ThrowableResultHandler throwableResultHandler;
private ObjectProvider<ThrowableResultHandler> throwableResultHandler;
public Stacktrace(ObjectProvider<ThrowableResultHandler> throwableResultHandler) {
this.throwableResultHandler = throwableResultHandler;
}
@ShellMethod(key = ThrowableResultHandler.DETAILS_COMMAND_NAME, value = "Display the full stacktrace of the last error.")
public void stacktrace() {
if (throwableResultHandler.getLastError() != null) {
throwableResultHandler.getLastError().printStackTrace(terminal.writer());
if (throwableResultHandler.getIfAvailable().getLastError() != null) {
throwableResultHandler.getIfAvailable().getLastError().printStackTrace(getTerminal().writer());
}
}
}

View File

@@ -117,8 +117,8 @@ public class HelpTest {
static class Config {
@Bean
public Help help() {
return new Help(Collections.singletonList(parameterResolver()));
public Help help(CommandRegistry commandRegistry) {
return new Help();
}
@Bean
@@ -148,7 +148,7 @@ public class HelpTest {
@Bean
public ParameterResolver parameterResolver() {
return new StandardParameterResolver(new DefaultConversionService());
return new StandardParameterResolver(new DefaultConversionService(), Collections.emptySet());
}
@Bean

View File

@@ -0,0 +1,76 @@
/*
* 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.standard;
import java.util.stream.Stream;
import org.jline.terminal.Terminal;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.shell.CommandRegistry;
import org.springframework.shell.ParameterResolver;
import org.springframework.shell.Shell;
/**
* Base class helping to build shell components.
*
* @author Janne Valkealahti
*/
public class AbstractShellComponent implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
private ObjectProvider<Shell> shellProvider;
private ObjectProvider<Terminal> terminalProvider;
private ObjectProvider<CommandRegistry> commandRegistryProvider;
private ObjectProvider<ParameterResolver> parameterResolverProvider;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
shellProvider = applicationContext.getBeanProvider(Shell.class);
terminalProvider = applicationContext.getBeanProvider(Terminal.class);
commandRegistryProvider = applicationContext.getBeanProvider(CommandRegistry.class);
parameterResolverProvider = applicationContext.getBeanProvider(ParameterResolver.class);
}
protected Shell getShell() {
return shellProvider.getObject();
}
protected Terminal getTerminal() {
return terminalProvider.getObject();
}
protected CommandRegistry getCommandRegistry() {
return commandRegistryProvider.getObject();
}
protected Stream<ParameterResolver> getParameterResolver() {
return parameterResolverProvider.orderedStream();
}
}

View File

@@ -16,21 +16,32 @@
package org.springframework.shell.standard;
import static org.springframework.util.StringUtils.collectionToDelimitedString;
import java.lang.reflect.Method;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.shell.*;
import org.springframework.shell.Availability;
import org.springframework.shell.Command;
import org.springframework.shell.ConfigurableCommandRegistry;
import org.springframework.shell.MethodTarget;
import org.springframework.shell.MethodTargetRegistrar;
import org.springframework.shell.Utils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import static org.springframework.util.StringUtils.collectionToDelimitedString;
/**
* The standard implementation of {@link MethodTargetRegistrar} for new shell
* applications, resolves methods annotated with {@link ShellMethod} on
@@ -40,13 +51,13 @@ import org.springframework.util.StringUtils;
* @author Florent Biville
* @author Camilo Gonzalez
*/
public class StandardMethodTargetRegistrar implements MethodTargetRegistrar {
public class StandardMethodTargetRegistrar implements MethodTargetRegistrar, ApplicationContextAware {
private ApplicationContext applicationContext;
private Map<String, MethodTarget> commands = new HashMap<>();
@Autowired
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}

View File

@@ -35,7 +35,6 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.metadata.MethodDescriptor;
@@ -97,6 +96,8 @@ public class StandardParameterResolver implements ParameterResolver {
private Collection<ValueProvider> valueProviders = new HashSet<>();
private Validator validator = Utils.defaultValidator();
/**
* A cache from method+input to String representation of actual parameter values. Note
* that the converted result is not cached, to allow dynamic computation to happen at
@@ -104,24 +105,16 @@ public class StandardParameterResolver implements ParameterResolver {
*/
private final Map<CacheKey, Map<Parameter, ParameterRawValue>> parameterCache = new ConcurrentReferenceHashMap<>();
@Autowired
public StandardParameterResolver(ConversionService conversionService) {
public StandardParameterResolver(ConversionService conversionService, Set<ValueProvider> valueProviders) {
this.conversionService = conversionService;
}
@Autowired(required = false)
public void setValueProviders(Collection<ValueProvider> valueProviders) {
this.valueProviders = valueProviders;
}
private Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
@Autowired(required = false)
public void setValidatorFactory(ValidatorFactory validatorFactory) {
this.validator = validatorFactory.getValidator();
}
@Override
public boolean supports(MethodParameter parameter) {
boolean optOut = parameter.hasParameterAnnotation(ShellOption.class)

View File

@@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.shell.standard.StandardAPIAutoConfiguration

View File

@@ -17,7 +17,10 @@
package org.springframework.shell.standard;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.jline.reader.ParsedLine;
@@ -33,7 +36,6 @@ import org.springframework.shell.Utils;
import org.springframework.shell.ValueResult;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.shell.ValueResultAsserts.assertThat;
@@ -46,12 +48,14 @@ import static org.springframework.util.ReflectionUtils.findMethod;
*/
public class StandardParameterResolverTest {
private StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService());
// private StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(), Collections.emptySet());
// Tests for resolution
@Test
public void testParses() throws Exception {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
List<String> words = asList("--force --name --foo y".split(" "));
@@ -74,6 +78,8 @@ public class StandardParameterResolverTest {
@Test
public void testParsesWithMethodPrefix() throws Exception {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "prefixTest", String.class);
ValueResult result = resolver.resolve(Utils.createMethodParameter(method, 0),
@@ -83,6 +89,8 @@ public class StandardParameterResolverTest {
@Test
public void testParameterSpecifiedTwiceViaDifferentAliases() throws Exception {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
assertThatThrownBy(() -> {
@@ -95,6 +103,8 @@ public class StandardParameterResolverTest {
@Test
public void testParameterSpecifiedTwiceViaSameKey() throws Exception {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
assertThatThrownBy(() -> {
@@ -107,6 +117,8 @@ public class StandardParameterResolverTest {
@Test
public void testTooMuchInput() throws Exception {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
assertThatThrownBy(() -> {
@@ -119,6 +131,8 @@ public class StandardParameterResolverTest {
@Test
public void testIncompleteCommandResolution() throws Exception {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "shutdown", Remote.Delay.class);
assertThatThrownBy(() -> {
@@ -131,6 +145,8 @@ public class StandardParameterResolverTest {
@Test
public void testIncompleteCommandResolutionBigArity() throws Exception {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "add", List.class);
assertThatThrownBy(() -> {
@@ -143,6 +159,8 @@ public class StandardParameterResolverTest {
@Test
public void testUnresolvableArg() throws Exception {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
assertThatThrownBy(() -> {
@@ -157,6 +175,8 @@ public class StandardParameterResolverTest {
@Test
public void testParameterKeyNotYetSetAppearsInProposals() {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
List<String> completions = resolver.complete(
Utils.createMethodParameter(method, 1),
@@ -172,6 +192,8 @@ public class StandardParameterResolverTest {
@Test
public void testParameterKeyNotFullySpecified() {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
List<String> completions = resolver.complete(
Utils.createMethodParameter(method, 1),
@@ -187,6 +209,8 @@ public class StandardParameterResolverTest {
@Test
public void testNoMoreAvailableParameters() {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "zap", boolean.class, String.class, String.class, String.class);
List<String> completions = resolver.complete(
Utils.createMethodParameter(method, 2), // trying to complete --foo
@@ -197,6 +221,8 @@ public class StandardParameterResolverTest {
@Test
public void testNotTheRightTimeToCompleteThatParameter() {
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
Collections.emptySet());
Method method = findMethod(Remote.class, "shutdown", Remote.Delay.class);
List<String> completions = resolver.complete(
Utils.createMethodParameter(method, 0),
@@ -207,8 +233,10 @@ public class StandardParameterResolverTest {
@Test
public void testValueCompletionWithNonDefaultArity() {
resolver.setValueProviders(singletonList(new Remote.NumberValueProvider("12", "42", "7")));
Set<ValueProvider> valueProviders = new HashSet<>();
valueProviders.add(new Remote.NumberValueProvider("12", "42", "7"));
StandardParameterResolver resolver = new StandardParameterResolver(new DefaultConversionService(),
valueProviders);
Method[] methods = {
findMethod(org.springframework.shell.standard.Remote.class, "add", List.class),