Command Availability

Registered commands do not always make sense, due to the internal state of the application. For example, there may be a download command, but it only works once the user has used connect on a remote server. Now, if the user tries to use the download command, the shell should explain that the command exists but that it is not available at the time. Spring Shell lets you do that, even letting you provide a short explanation of the reason for the command not being available.spring-doc.cn

Programmatic

With programmatic registration you can use availability method which takes Supplier<Availability>.spring-doc.cn

private boolean connected;

@Bean
public CommandRegistration connect(
		CommandRegistration.BuilderSupplier builder) {
	return builder.get()
		.command("connect")
		.withOption()
			.longNames("connected")
			.required()
			.type(boolean.class)
			.and()
		.withTarget()
			.consumer(ctx -> {
				boolean connected = ctx.getOptionValue("connected");
				this.connected = connected;
			})
			.and()
		.build();
}

@Bean
public CommandRegistration download(
		CommandRegistration.BuilderSupplier builder) {
	return builder.get()
		.command("download")
		.availability(() -> {
			return connected
				? Availability.available()
				: Availability.unavailable("you are not connected");
		})
		.withTarget()
			.consumer(ctx -> {
				// do something
			})
			.and()
		.build();
}

Annotation

With annotation based commands you can use @CommandAvailability together with AvailabilityProvider.spring-doc.cn

@Command
class MyCommands {

	private boolean connected;

	@Command(command = "connect")
	public void connect(String user, String password) {
		connected = true;
	}


	@Command(command = "download")
	@CommandAvailability(provider = "downloadAvailability")
	public void download(
	) {
		// do something
	}

	@Bean
	public AvailabilityProvider downloadAvailability() {
		return () -> connected
			? Availability.available()
			: Availability.unavailable("you are not connected");
	}
}

Legacy Annotation

There are three possible ways for a command to indicate availability. They all use a no-arg method that returns an instance of Availability. Consider the following example:spring-doc.cn

@ShellComponent
public class MyCommands {

	private boolean connected;

	@ShellMethod("Connect to the server.")
	public void connect(String user, String password) {
		// do something
		connected = true;
	}

	@ShellMethod("Download the nuclear codes.")
	public void download() {
		// do something
	}

	public Availability downloadAvailability() {
		return connected
			? Availability.available()
			: Availability.unavailable("you are not connected");
	}
}

The connect method is used to connect to the server (details omitted), altering the state of the command through the connected boolean when done. The download command as marked as unavailable until the user has connected, thanks to the presence of a method named exactly as the download command method with the Availability suffix in its name. The method returns an instance of Availability, constructed with one of the two factory methods. If the command is not available, an explanation has to be provided. Now, if the user tries to invoke the command while not being connected, here is what happens:spring-doc.cn

shell:>download
Command 'download' exists but is not currently available because you are not connected.
Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.

Information about currently unavailable commands is also used in the integrated help. See Help.spring-doc.cn

The reason provided when the command is not available should read nicely if appended after “Because”.spring-doc.cn

You should not start the sentence with a capital or add a final periodspring-doc.cn

If naming the availability method after the name of the command method does not suit you, you can provide an explicit name by using the @ShellMethodAvailability annotation:spring-doc.cn

@ShellMethod("Download the nuclear codes.")
@ShellMethodAvailability("availabilityCheck") (1)
public void download() {
}

public Availability availabilityCheck() { (1)
	return connected
		? Availability.available()
		: Availability.unavailable("you are not connected");
}
1 the names have to match

Finally, it is often the case that several commands in the same class share the same internal state and, thus, should all be available or unavailable as a group. Instead of having to stick the @ShellMethodAvailability on all command methods, Spring Shell lets you flip things around and put the @ShellMethodAvailabilty annotation on the availability method, specifying the names of the commands that it controls:spring-doc.cn

@ShellMethod("Download the nuclear codes.")
public void download() {
}

@ShellMethod("Disconnect from the server.")
public void disconnect() {
}

@ShellMethodAvailability({"download", "disconnect"})
public Availability availabilityCheck() {
	return connected
		? Availability.available()
		: Availability.unavailable("you are not connected");
}

The default value for the @ShellMethodAvailability.value() attribute is *. This special wildcard matches all command names. This makes it easy to turn all commands of a single class on or off with a single availability method:spring-doc.cn

@ShellComponent
public class Toggles {

	@ShellMethodAvailability
	public Availability availabilityOnWeekdays() {
		return Calendar.getInstance().get(DAY_OF_WEEK) == SUNDAY
			? Availability.available()
			: Availability.unavailable("today is not Sunday");
	}

	@ShellMethod
	public void foo() {}

	@ShellMethod
	public void bar() {}
}
Spring Shell does not impose many constraints on how to write commands and how to organize classes. However, it is often good practice to put related commands in the same class, and the availability indicators can benefit from that.