Skip to content

Commit 39c01fe

Browse files
committed
Fix availability feature
- Bring back some missing functionality which got missing during the rework to new command model. - Polish some classes. - Restore origin sample. - Add availability things into help templates and its representation model. - Fixes #423
1 parent b2f96e6 commit 39c01fe

File tree

10 files changed

+208
-41
lines changed

10 files changed

+208
-41
lines changed
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017 the original author or authors.
2+
* Copyright 2017-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -13,7 +13,6 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
1716
package org.springframework.shell;
1817

1918
/**
@@ -23,20 +22,21 @@
2322
*/
2423
public class CommandNotCurrentlyAvailable extends RuntimeException {
2524

26-
private final String command;
27-
private final Availability availability;
25+
private final String command;
26+
private final Availability availability;
2827

29-
public CommandNotCurrentlyAvailable(String command, Availability availability) {
30-
super(String.format("Command '%s' exists but is not currently available because %s", command, availability.getReason()));
31-
this.command = command;
32-
this.availability = availability;
33-
}
28+
public CommandNotCurrentlyAvailable(String command, Availability availability) {
29+
super(String.format("Command '%s' exists but is not currently available because %s", command,
30+
availability.getReason()));
31+
this.command = command;
32+
this.availability = availability;
33+
}
3434

35-
public String getCommand() {
36-
return command;
37-
}
35+
public String getCommand() {
36+
return command;
37+
}
3838

39-
public Availability getAvailability() {
40-
return availability;
41-
}
39+
public Availability getAvailability() {
40+
return availability;
41+
}
4242
}

spring-shell-core/src/main/java/org/springframework/shell/command/CommandExecution.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.springframework.messaging.Message;
3030
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
3131
import org.springframework.messaging.support.MessageBuilder;
32+
import org.springframework.shell.Availability;
33+
import org.springframework.shell.CommandNotCurrentlyAvailable;
3234
import org.springframework.shell.command.CommandParser.CommandParserException;
3335
import org.springframework.shell.command.CommandParser.CommandParserResults;
3436
import org.springframework.shell.command.CommandRegistration.TargetInfo;
@@ -95,6 +97,11 @@ public DefaultCommandExecution(List<? extends HandlerMethodArgumentResolver> res
9597
}
9698

9799
public Object evaluate(CommandRegistration registration, String[] args) {
100+
// fast fail with availability before doing anything else
101+
Availability availability = registration.getAvailability();
102+
if (availability != null && !availability.isAvailable()) {
103+
return new CommandNotCurrentlyAvailable(registration.getCommand(), availability);
104+
}
98105

99106
List<CommandOption> options = registration.getOptions();
100107
CommandParser parser = CommandParser.of(conversionService);

spring-shell-core/src/test/java/org/springframework/shell/command/CommandExecutionTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import org.springframework.core.convert.ConversionService;
2727
import org.springframework.core.convert.support.DefaultConversionService;
2828
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
29+
import org.springframework.shell.Availability;
30+
import org.springframework.shell.CommandNotCurrentlyAvailable;
2931
import org.springframework.shell.command.CommandExecution.CommandParserExceptionsException;
3032
import org.springframework.shell.command.CommandRegistration.OptionArity;
3133

@@ -459,4 +461,22 @@ public void testRequiredArg() {
459461
execution.evaluate(r1, new String[]{});
460462
}).isInstanceOf(CommandParserExceptionsException.class);
461463
}
464+
465+
@Test
466+
public void testCommandNotAvailable() {
467+
CommandRegistration r1 = CommandRegistration.builder()
468+
.command("command1")
469+
.description("help")
470+
.withOption()
471+
.longNames("arg1")
472+
.description("some arg1")
473+
.and()
474+
.availability(() -> Availability.unavailable("fake reason"))
475+
.withTarget()
476+
.function(function1)
477+
.and()
478+
.build();
479+
Object result = execution.evaluate(r1, new String[]{"--arg1", "myarg1value"});
480+
assertThat(result).isInstanceOf(CommandNotCurrentlyAvailable.class);
481+
}
462482
}

spring-shell-core/src/test/java/org/springframework/shell/command/CommandRegistrationTests.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.junit.jupiter.api.Test;
1919

2020
import org.springframework.core.ResolvableType;
21+
import org.springframework.shell.Availability;
2122
import org.springframework.shell.command.CommandRegistration.OptionArity;
2223
import org.springframework.shell.command.CommandRegistration.TargetInfo.TargetType;
2324
import org.springframework.shell.context.InteractionMode;
@@ -410,4 +411,27 @@ public void testExitCodes() {
410411
assertThat(registration.getExitCode()).isNotNull();
411412
assertThat(registration.getExitCode().getMappingFunctions()).hasSize(4);
412413
}
414+
415+
@Test
416+
public void testAvailability() {
417+
CommandRegistration registration;
418+
registration = CommandRegistration.builder()
419+
.command("command1")
420+
.withTarget()
421+
.function(function1)
422+
.and()
423+
.build();
424+
assertThat(registration.getAvailability()).isNotNull();
425+
assertThat(registration.getAvailability().isAvailable()).isTrue();
426+
427+
registration = CommandRegistration.builder()
428+
.command("command1")
429+
.availability(() -> Availability.unavailable("fake"))
430+
.withTarget()
431+
.function(function1)
432+
.and()
433+
.build();
434+
assertThat(registration.getAvailability()).isNotNull();
435+
assertThat(registration.getAvailability().isAvailable()).isFalse();
436+
}
413437
}

spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/DynamicCommands.java

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,29 +29,37 @@
2929
@ShellComponent
3030
public class DynamicCommands {
3131

32-
private boolean connected;
32+
private boolean connected;
3333

34-
public Availability authenticateAvailability() {
35-
return connected ? Availability.available() : Availability.unavailable("you are not connected");
36-
}
34+
private boolean authenticated;
3735

38-
@ShellMethod(value = "Authenticate with the system", group = "Dynamic Commands")
39-
public void authenticate(String credentials) {
40-
}
36+
public Availability authenticateAvailability() {
37+
return connected ? Availability.available() : Availability.unavailable("you are not connected");
38+
}
4139

42-
@ShellMethod(value = "Connect to the system", group = "Dynamic Commands")
43-
public void connect() {
44-
connected = true;
45-
}
40+
@ShellMethod(value = "Authenticate with the system", group = "Dynamic Commands")
41+
public void authenticate(String credentials) {
42+
authenticated = "sesame".equals(credentials);
43+
}
4644

47-
@ShellMethod(value = "Disconnect from the system", group = "Dynamic Commands")
48-
public void disconnect() {
49-
connected = false;
50-
}
45+
@ShellMethod(value = "Connect to the system", group = "Dynamic Commands")
46+
public void connect() {
47+
connected = true;
48+
}
5149

52-
@ShellMethod(value = "Blow Everything up", group = "Dynamic Commands")
53-
@ShellMethodAvailability("dangerousAvailability")
54-
public String blowUp() {
55-
return "Boom!";
56-
}
50+
@ShellMethod(value = "Disconnect from the system", group = "Dynamic Commands")
51+
public void disconnect() {
52+
connected = false;
53+
}
54+
55+
@ShellMethod(value = "Blow Everything up", group = "Dynamic Commands")
56+
@ShellMethodAvailability("dangerousAvailability")
57+
public String blowUp() {
58+
return "Boom!";
59+
}
60+
61+
public Availability dangerousAvailability() {
62+
return connected && authenticated ? Availability.available()
63+
: Availability.unavailable("you failed to authenticate. Try 'sesame'.");
64+
}
5765
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright 2022 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.standard.commands;
17+
18+
/**
19+
* Model encapsulating info about {@code command availability}.
20+
*
21+
* @author Janne Valkealahti
22+
*/
23+
class CommandAvailabilityInfoModel {
24+
25+
private boolean available;
26+
private String reason;
27+
28+
CommandAvailabilityInfoModel(boolean available, String reason) {
29+
this.available = available;
30+
this.reason = reason;
31+
}
32+
33+
/**
34+
* Builds {@link CommandAvailabilityInfoModel}.
35+
*
36+
* @param available the available flag
37+
* @param reason the reason
38+
* @return a command parameter availability model
39+
*/
40+
static CommandAvailabilityInfoModel of(boolean available, String reason) {
41+
return new CommandAvailabilityInfoModel(available, reason);
42+
}
43+
44+
public boolean getAvailable() {
45+
return available;
46+
}
47+
48+
public String getReason() {
49+
return reason;
50+
}
51+
}

spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/CommandInfoModel.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.stream.Collectors;
2020
import java.util.stream.Stream;
2121

22+
import org.springframework.shell.Availability;
2223
import org.springframework.shell.command.CommandOption;
2324
import org.springframework.shell.command.CommandRegistration;
2425
import org.springframework.util.ClassUtils;
@@ -34,11 +35,14 @@ class CommandInfoModel {
3435
private String name;
3536
private String description;
3637
private List<CommandParameterInfoModel> parameters;
38+
private CommandAvailabilityInfoModel availability;
3739

38-
CommandInfoModel(String name, String description, List<CommandParameterInfoModel> parameters) {
40+
CommandInfoModel(String name, String description, List<CommandParameterInfoModel> parameters,
41+
CommandAvailabilityInfoModel availability) {
3942
this.name = name;
4043
this.description = description;
4144
this.parameters = parameters;
45+
this.availability = availability;
4246
}
4347

4448
/**
@@ -65,7 +69,15 @@ static CommandInfoModel of(String name, CommandRegistration registration) {
6569
.collect(Collectors.toList());
6670

6771
String description = registration.getDescription();
68-
return new CommandInfoModel(name, description, parameters);
72+
boolean available = true;
73+
String availReason = "";
74+
if (registration.getAvailability() != null) {
75+
Availability a = registration.getAvailability();
76+
available = a.isAvailable();
77+
availReason = a.getReason();
78+
}
79+
CommandAvailabilityInfoModel availModel = CommandAvailabilityInfoModel.of(available, availReason);
80+
return new CommandInfoModel(name, description, parameters, availModel);
6981
}
7082

7183
private static String commandOptionType(CommandOption o) {
@@ -88,4 +100,8 @@ public String getDescription() {
88100
public List<CommandParameterInfoModel> getParameters() {
89101
return parameters;
90102
}
103+
104+
public CommandAvailabilityInfoModel getAvailability() {
105+
return availability;
106+
}
91107
}

spring-shell-standard-commands/src/main/java/org/springframework/shell/standard/commands/GroupsInfoModel.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,14 @@ class GroupsInfoModel {
3636
private boolean showGroups = true;
3737
private final List<GroupCommandInfoModel> groups;
3838
private final List<CommandInfoModel> commands;
39+
private boolean hasUnavailableCommands = false;
3940

40-
GroupsInfoModel(boolean showGroups, List<GroupCommandInfoModel> groups, List<CommandInfoModel> commands) {
41+
GroupsInfoModel(boolean showGroups, List<GroupCommandInfoModel> groups, List<CommandInfoModel> commands,
42+
boolean hasUnavailableCommands) {
4143
this.showGroups = showGroups;
4244
this.groups = groups;
4345
this.commands = commands;
46+
this.hasUnavailableCommands = hasUnavailableCommands;
4447
}
4548

4649
/**
@@ -71,7 +74,17 @@ static GroupsInfoModel of(boolean showGroups, Map<String, CommandRegistration> r
7174
List<CommandInfoModel> commands = gcims.stream()
7275
.flatMap(gcim -> gcim.getCommands().stream())
7376
.collect(Collectors.toList());
74-
return new GroupsInfoModel(showGroups, gcims, commands);
77+
boolean hasUnavailableCommands = commands.stream()
78+
.map(c -> {
79+
if (c.getAvailability() != null) {
80+
return c.getAvailability().getAvailable();
81+
}
82+
return true;
83+
})
84+
.filter(a -> !a)
85+
.findFirst()
86+
.isPresent();
87+
return new GroupsInfoModel(showGroups, gcims, commands, hasUnavailableCommands);
7588
}
7689

7790
public boolean getShowGroups() {
@@ -85,4 +98,8 @@ public List<GroupCommandInfoModel> getGroups() {
8598
public List<CommandInfoModel> getCommands() {
8699
return commands;
87100
}
101+
102+
public boolean getHasUnavailableCommands() {
103+
return hasUnavailableCommands;
104+
}
88105
}

spring-shell-standard-commands/src/main/resources/template/help-command-default.stg

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,21 @@ options(options) ::= <<
5757
<options:{ o | <option(o)>}>
5858
>>
5959

60+
// AVAILABILITY
61+
availability(availability) ::= <<
62+
<if(!availability.available)>
63+
64+
<("CURRENTLY UNAVAILABLE"); format="style-highlight">
65+
<availability.reason>
66+
<endif>
67+
>>
68+
6069
// main
6170
main(model) ::= <<
6271
<name(model.name, model.description)>
6372

6473
<synopsis(model.name, model.parameters)>
6574

6675
<options(model.parameters)>
76+
<availability(model.availability)>
6777
>>

0 commit comments

Comments
 (0)