Skip to content

Custom "Converters" not working with @Command-Annotations #1352

@tschoellhorn

Description

@tschoellhorn

It seems that custom "Converter<S, T>"-Beans are not registered correctly when running a SpringBoot-Application and SpringShell. I have attached a full working project with test-cases demonstrating the problem: shell.zip

In the previous 3.x-line of SpringShell this was not a issue.

In a nutshell:

We have commands in the form:

@Command(name = "custom")
public void custom(
	@Option(longName = "msg", required = true) Message msg) {	
	System.out.println(msg.toString());
}

In a Component we define a new Bean implementiung the Converter-Interface. This is initialized from SpringBoot before running the actual command - and yet the Converter cannot be found from the CommandExecutor:

org.springframework.shell.core.command.CommandExecutionException: Unable to execute command custom
	at org.springframework.shell.core.command.CommandExecutor.execute(CommandExecutor.java:74) ~[spring-shell-core-4.0.2.jar:4.0.2]
	at org.springframework.shell.core.NonInteractiveShellRunner.executeCommand(NonInteractiveShellRunner.java:158) ~[spring-shell-core-4.0.2.jar:4.0.2]
	at org.springframework.shell.core.NonInteractiveShellRunner.run(NonInteractiveShellRunner.java:95) ~[spring-shell-core-4.0.2.jar:4.0.2]
	at org.springframework.shell.core.autoconfigure.ShellRunnerAutoConfiguration.lambda$springShellApplicationRunner$0(ShellRunnerAutoConfiguration.java:39) ~[spring-shell-core-autoconfigure-4.0.2.jar:4.0.2]
	at org.springframework.boot.SpringApplication.lambda$callRunner$0(SpringApplication.java:788) ~[spring-boot-4.0.6.jar:4.0.6]
	at org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:82) ~[spring-core-7.0.7.jar:7.0.7]
	at org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60) ~[spring-core-7.0.7.jar:7.0.7]
	at org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:86) ~[spring-core-7.0.7.jar:7.0.7]
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800) ~[spring-boot-4.0.6.jar:4.0.6]
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:788) ~[spring-boot-4.0.6.jar:4.0.6]
	at org.springframework.boot.SpringApplication.lambda$callRunners$0(SpringApplication.java:776) ~[spring-boot-4.0.6.jar:4.0.6]
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) ~[na:na]
	at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) ~[na:na]
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) ~[na:na]
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[na:na]
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:776) ~[spring-boot-4.0.6.jar:4.0.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:328) ~[spring-boot-4.0.6.jar:4.0.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1365) ~[spring-boot-4.0.6.jar:4.0.6]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-4.0.6.jar:4.0.6]
	at shell.SpringShellApplication.main(SpringShellApplication.java:14) ~[classes/:na]
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [shell.SpringShellApplication$Message]
	at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:289) ~[spring-core-7.0.7.jar:7.0.7]
	at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:184) ~[spring-core-7.0.7.jar:7.0.7]
	at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:165) ~[spring-core-7.0.7.jar:7.0.7]
	at org.springframework.shell.core.command.adapter.MethodInvokerCommandAdapter.prepareArguments(MethodInvokerCommandAdapter.java:153) ~[spring-shell-core-4.0.2.jar:4.0.2]
	at org.springframework.shell.core.command.adapter.MethodInvokerCommandAdapter.doExecute(MethodInvokerCommandAdapter.java:93) ~[spring-shell-core-4.0.2.jar:4.0.2]
	at org.springframework.shell.core.command.AbstractCommand.execute(AbstractCommand.java:166) ~[spring-shell-core-4.0.2.jar:4.0.2]
	at org.springframework.shell.core.command.CommandExecutor.execute(CommandExecutor.java:71) ~[spring-shell-core-4.0.2.jar:4.0.2]
	... 23 common frames omitted

From my point of view this should be possible as it is standard Spring-Behaviour to support "Converters".

Additional note: When registering the Command programmitically the Converters are respected. It seems to me, that the Building-process of the Commands via Annotations is the problem.

@Bean
org.springframework.shell.core.command.Command programmatic() {
	Consumer<CommandContext> action = ctx -> 
		System.out.println(ctx.getOptionByName("msg"));
	
	return org.springframework.shell.core.command.Command
		.builder()
		.name("programmatic")
		.options(
			CommandOption.with()
				.longName("msg")
				.type(Message.class).build())
		.execute(action);
}
``

Metadata

Metadata

Assignees

No one assigned

    Labels

    status/need-triageTeam needs to triage and take a first look

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions