Skip to content

Commit 0db1bbc

Browse files
committed
Document command availability
- Docs for annotation and registration - Remove dynamic from header - Move samples to snippets - Backport #1053 - Fixes #1073
1 parent 05ad858 commit 0db1bbc

File tree

2 files changed

+231
-69
lines changed

2 files changed

+231
-69
lines changed

spring-shell-docs/modules/ROOT/pages/commands/availability.adoc

Lines changed: 35 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
[[dynamic-command-availability]]
2-
= Dynamic Command Availability
2+
= Command Availability
3+
4+
ifndef::snippets[:snippets: ../../../../src/test/java/org/springframework/shell/docs]
35

46
Registered commands do not always make sense, due to the internal state of the application.
57
For example, there may be a `download` command, but it only works once the user has used `connect` on a remote
@@ -8,34 +10,35 @@ the command exists but that it is not available at the time.
810
Spring Shell lets you do that, even letting you provide a short explanation of the reason for
911
the command not being available.
1012

13+
== Programmatic
14+
15+
With programmatic registration you can use `availability` method which takes
16+
`Supplier<Availability>`.
17+
18+
[source, java, indent=0]
19+
----
20+
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-programmatic]
21+
----
22+
23+
== Annotation
24+
25+
With annotation based commands you can use `@CommandAvailability` together with
26+
`AvailabilityProvider`.
27+
28+
[source, java, indent=0]
29+
----
30+
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-annotation]
31+
----
32+
33+
== Legacy Annotation
34+
1135
There are three possible ways for a command to indicate availability.
1236
They all use a no-arg method that returns an instance of `Availability`.
1337
Consider the following example:
1438

15-
[source, java]
39+
[source, java, indent=0]
1640
----
17-
@ShellComponent
18-
public class MyCommands {
19-
20-
private boolean connected;
21-
22-
@ShellMethod("Connect to the server.")
23-
public void connect(String user, String password) {
24-
[...]
25-
connected = true;
26-
}
27-
28-
@ShellMethod("Download the nuclear codes.")
29-
public void download() {
30-
[...]
31-
}
32-
33-
public Availability downloadAvailability() {
34-
return connected
35-
? Availability.available()
36-
: Availability.unavailable("you are not connected");
37-
}
38-
}
41+
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-in-shellcomponent]
3942
----
4043

4144
The `connect` method is used to connect to the server (details omitted), altering the state
@@ -65,45 +68,21 @@ You should not start the sentence with a capital or add a final period
6568
If naming the availability method after the name of the command method does not suit you, you
6669
can provide an explicit name by using the `@ShellMethodAvailability` annotation:
6770

68-
[source, java]
71+
[source, java, indent=0]
6972
----
70-
@ShellMethod("Download the nuclear codes.")
71-
@ShellMethodAvailability("availabilityCheck") // <1>
72-
public void download() {
73-
[...]
74-
}
75-
76-
public Availability availabilityCheck() { // <1>
77-
return connected
78-
? Availability.available()
79-
: Availability.unavailable("you are not connected");
80-
}
73+
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-name-in-shellcomponent]
8174
----
75+
8276
<1> the names have to match
8377

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

89-
[source, java]
83+
[source, java, indent=0]
9084
----
91-
@ShellMethod("Download the nuclear codes.")
92-
public void download() {
93-
[...]
94-
}
95-
96-
@ShellMethod("Disconnect from the server.")
97-
public void disconnect() {
98-
[...]
99-
}
100-
101-
@ShellMethodAvailability({"download", "disconnect"})
102-
public Availability availabilityCheck() {
103-
return connected
104-
? Availability.available()
105-
: Availability.unavailable("you are not connected");
106-
}
85+
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-name-multi-in-shellcomponent]
10786
----
10887

10988
[TIP]
@@ -112,24 +91,11 @@ The default value for the `@ShellMethodAvailability.value()` attribute is `*`. T
11291
wildcard matches all command names. This makes it easy to turn all commands of a single class on or off
11392
with a single availability method:
11493
115-
[source,java]
94+
[source, java, indent=0]
11695
----
117-
@ShellComponent
118-
public class Toggles {
119-
@ShellMethodAvailability
120-
public Availability availabilityOnWeekdays() {
121-
return Calendar.getInstance().get(DAY_OF_WEEK) == SUNDAY
122-
? Availability.available()
123-
: Availability.unavailable("today is not Sunday");
124-
}
125-
126-
@ShellMethod
127-
public void foo() {}
128-
129-
@ShellMethod
130-
public void bar() {}
131-
}
96+
include::{snippets}/CommandAvailabilitySnippets.java[tag=availability-method-default-value-in-shellcomponent]
13297
----
98+
13399
=====
134100

135101
TIP: Spring Shell does not impose many constraints on how to write commands and how to organize classes.
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.shell.docs;
17+
18+
import java.util.Calendar;
19+
20+
import org.springframework.context.annotation.Bean;
21+
import org.springframework.shell.Availability;
22+
import org.springframework.shell.AvailabilityProvider;
23+
import org.springframework.shell.command.CommandRegistration;
24+
import org.springframework.shell.command.annotation.Command;
25+
import org.springframework.shell.command.annotation.CommandAvailability;
26+
import org.springframework.shell.standard.ShellComponent;
27+
import org.springframework.shell.standard.ShellMethod;
28+
import org.springframework.shell.standard.ShellMethodAvailability;
29+
30+
import static java.util.Calendar.DAY_OF_WEEK;
31+
import static java.util.Calendar.SUNDAY;
32+
33+
class CommandAvailabilitySnippets {
34+
35+
class Dump1 {
36+
// tag::availability-method-in-shellcomponent[]
37+
@ShellComponent
38+
public class MyCommands {
39+
40+
private boolean connected;
41+
42+
@ShellMethod("Connect to the server.")
43+
public void connect(String user, String password) {
44+
// do something
45+
connected = true;
46+
}
47+
48+
@ShellMethod("Download the nuclear codes.")
49+
public void download() {
50+
// do something
51+
}
52+
53+
public Availability downloadAvailability() {
54+
return connected
55+
? Availability.available()
56+
: Availability.unavailable("you are not connected");
57+
}
58+
}
59+
// end::availability-method-in-shellcomponent[]
60+
}
61+
62+
class Dump2 {
63+
boolean connected;
64+
65+
// tag::availability-method-name-in-shellcomponent[]
66+
@ShellMethod("Download the nuclear codes.")
67+
@ShellMethodAvailability("availabilityCheck") // <1>
68+
public void download() {
69+
}
70+
71+
public Availability availabilityCheck() { // <1>
72+
return connected
73+
? Availability.available()
74+
: Availability.unavailable("you are not connected");
75+
}
76+
// end::availability-method-name-in-shellcomponent[]
77+
}
78+
79+
class Dump3 {
80+
boolean connected;
81+
// tag::availability-method-name-multi-in-shellcomponent[]
82+
@ShellMethod("Download the nuclear codes.")
83+
public void download() {
84+
}
85+
86+
@ShellMethod("Disconnect from the server.")
87+
public void disconnect() {
88+
}
89+
90+
@ShellMethodAvailability({"download", "disconnect"})
91+
public Availability availabilityCheck() {
92+
return connected
93+
? Availability.available()
94+
: Availability.unavailable("you are not connected");
95+
}
96+
97+
// end::availability-method-name-multi-in-shellcomponent[]
98+
}
99+
100+
// tag::availability-method-default-value-in-shellcomponent[]
101+
@ShellComponent
102+
public class Toggles {
103+
104+
@ShellMethodAvailability
105+
public Availability availabilityOnWeekdays() {
106+
return Calendar.getInstance().get(DAY_OF_WEEK) == SUNDAY
107+
? Availability.available()
108+
: Availability.unavailable("today is not Sunday");
109+
}
110+
111+
@ShellMethod
112+
public void foo() {}
113+
114+
@ShellMethod
115+
public void bar() {}
116+
}
117+
// end::availability-method-default-value-in-shellcomponent[]
118+
119+
class Dump4 {
120+
// tag::availability-method-annotation[]
121+
@Command
122+
class MyCommands {
123+
124+
private boolean connected;
125+
126+
@Command(command = "connect")
127+
public void connect(String user, String password) {
128+
connected = true;
129+
}
130+
131+
132+
@Command(command = "download")
133+
@CommandAvailability(provider = "downloadAvailability")
134+
public void download(
135+
) {
136+
// do something
137+
}
138+
139+
@Bean
140+
public AvailabilityProvider downloadAvailability() {
141+
return () -> connected
142+
? Availability.available()
143+
: Availability.unavailable("you are not connected");
144+
}
145+
}
146+
147+
// end::availability-method-annotation[]
148+
}
149+
150+
151+
class Dump5 {
152+
153+
// tag::availability-method-programmatic[]
154+
private boolean connected;
155+
156+
@Bean
157+
public CommandRegistration connect(
158+
CommandRegistration.BuilderSupplier builder) {
159+
return builder.get()
160+
.command("connect")
161+
.withOption()
162+
.longNames("connected")
163+
.required()
164+
.type(boolean.class)
165+
.and()
166+
.withTarget()
167+
.consumer(ctx -> {
168+
boolean connected = ctx.getOptionValue("connected");
169+
this.connected = connected;
170+
})
171+
.and()
172+
.build();
173+
}
174+
175+
@Bean
176+
public CommandRegistration download(
177+
CommandRegistration.BuilderSupplier builder) {
178+
return builder.get()
179+
.command("download")
180+
.availability(() -> {
181+
return connected
182+
? Availability.available()
183+
: Availability.unavailable("you are not connected");
184+
})
185+
.withTarget()
186+
.consumer(ctx -> {
187+
// do something
188+
})
189+
.and()
190+
.build();
191+
}
192+
// end::availability-method-programmatic[]
193+
194+
}
195+
196+
}

0 commit comments

Comments
 (0)