diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ParameterResolverAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ParameterResolverAutoConfiguration.java index 4ec18d23..3c8c9c17 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ParameterResolverAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/ParameterResolverAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 the original author or authors. + * Copyright 2022-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.springframework.messaging.handler.invocation.HandlerMethodArgumentRes import org.springframework.shell.command.ArgumentHeaderMethodArgumentResolver; import org.springframework.shell.command.CommandContextMethodArgumentResolver; import org.springframework.shell.command.CommandExecution.CommandExecutionHandlerMethodArgumentResolvers; +import org.springframework.shell.command.annotation.support.OptionMethodArgumentResolver; import org.springframework.shell.completion.CompletionResolver; import org.springframework.shell.completion.RegistrationOptionsCompletionResolver; import org.springframework.shell.config.ShellConversionServiceSupplier; @@ -46,6 +47,7 @@ public class ParameterResolverAutoConfiguration { resolvers.add(new HeadersMethodArgumentResolver()); resolvers.add(new CommandContextMethodArgumentResolver()); resolvers.add(new ShellOptionMethodArgumentResolver(shellConversionServiceSupplier.get(), null)); + resolvers.add(new OptionMethodArgumentResolver(shellConversionServiceSupplier.get(), null)); return new CommandExecutionHandlerMethodArgumentResolvers(resolvers); } } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/OptionMethodArgumentResolver.java b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/OptionMethodArgumentResolver.java new file mode 100644 index 00000000..54e943f2 --- /dev/null +++ b/spring-shell-core/src/main/java/org/springframework/shell/command/annotation/support/OptionMethodArgumentResolver.java @@ -0,0 +1,84 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.shell.command.annotation.support; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.core.MethodParameter; +import org.springframework.core.convert.ConversionService; +import org.springframework.lang.Nullable; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHandlingException; +import org.springframework.shell.command.annotation.Option; +import org.springframework.shell.support.AbstractArgumentMethodArgumentResolver; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Resolver for {@link Option @Option} arguments. + * + * @author Janne Valkealahti + */ +public class OptionMethodArgumentResolver extends AbstractArgumentMethodArgumentResolver { + + public OptionMethodArgumentResolver(ConversionService conversionService, + @Nullable ConfigurableBeanFactory beanFactory) { + super(conversionService, beanFactory); + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(Option.class); + } + + @Override + protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { + Option annot = parameter.getParameterAnnotation(Option.class); + Assert.state(annot != null, "No Option annotation"); + List names = Arrays.stream(annot != null ? annot.longNames() : new String[0]) + .collect(Collectors.toList()); + return new HeaderNamedValueInfo(annot, names); + } + + @Override + @Nullable + protected Object resolveArgumentInternal(MethodParameter parameter, Message message, List names) + throws Exception { + for (String name : names) { + if (message.getHeaders().containsKey(ARGUMENT_PREFIX + name)) { + return message.getHeaders().get(ARGUMENT_PREFIX + name); + } + } + return null; + } + + @Override + protected void handleMissingValue(List headerName, MethodParameter parameter, Message message) { + throw new MessageHandlingException(message, + "Missing headers '" + StringUtils.collectionToCommaDelimitedString(headerName) + + "' for method parameter type [" + parameter.getParameterType() + "]"); + } + + private static final class HeaderNamedValueInfo extends NamedValueInfo { + + private HeaderNamedValueInfo(Option annotation, List names) { + super(names, false, null); + } + } +} diff --git a/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/OptionNamingCommands.java b/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/OptionNamingCommands.java index 7faa35a7..3d22b49e 100644 --- a/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/OptionNamingCommands.java +++ b/spring-shell-samples/src/main/java/org/springframework/shell/samples/e2e/OptionNamingCommands.java @@ -17,6 +17,8 @@ package org.springframework.shell.samples.e2e; import org.springframework.context.annotation.Bean; import org.springframework.shell.command.CommandRegistration; +import org.springframework.shell.command.annotation.Command; +import org.springframework.shell.command.annotation.Option; import org.springframework.shell.standard.ShellComponent; import org.springframework.shell.standard.ShellMethod; import org.springframework.shell.standard.ShellOption; @@ -28,12 +30,28 @@ public class OptionNamingCommands { public static class LegacyAnnotation extends BaseE2ECommands { @ShellMethod(key = LEGACY_ANNO + "option-naming-1", group = GROUP) - public void testOptionNaming1Annotation( + public String testOptionNaming1Annotation( @ShellOption("from_snake") String snake, @ShellOption("fromCamel") String camel, @ShellOption("from-kebab") String kebab, @ShellOption("FromPascal") String pascal ) { + return String.format("snake='%s' camel='%s' kebab='%s' pascal='%s' ", snake, camel, kebab, pascal); + } + + } + + @Command(command = BaseE2ECommands.ANNO, group = BaseE2ECommands.GROUP) + public static class Annotation extends BaseE2ECommands { + + @Command(command = "option-naming-1") + public String testOptionNaming1Annotation( + @Option(longNames = "from_snake") String snake, + @Option(longNames = "fromCamel") String camel, + @Option(longNames = "from-kebab") String kebab, + @Option(longNames = "FromPascal") String pascal + ) { + return String.format("snake='%s' camel='%s' kebab='%s' pascal='%s' ", snake, camel, kebab, pascal); } } @@ -65,10 +83,16 @@ public class OptionNamingCommands { .withOption() .longNames("arg1") .nameModifier(name -> "x" + name) - .required() + // .required() .and() .withTarget() - .consumer(ctx -> {}) + .function(ctx -> { + String snake = ctx.getOptionValue("from_snake"); + String camel = ctx.getOptionValue("fromCamel"); + String kebab = ctx.getOptionValue("from-kebab"); + String pascal = ctx.getOptionValue("FromPascal"); + return String.format("snake='%s' camel='%s' kebab='%s' pascal='%s' ", snake, camel, kebab, pascal); + }) .and() .build(); }