Add dynamic command availability
Introduce availability concept on MethodTarget (with reason if not available) Add bridge to @CliAvailabilityIndicator to Legacy registrar Fixes #138 Add help for unavailable commands Add standard API for availability
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Indicates whether or not a command is currently available. When not available, provides
|
||||
* a reason.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class Availability {
|
||||
|
||||
private final String reason;
|
||||
|
||||
private Availability(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public static Availability available() {
|
||||
return new Availability(null);
|
||||
}
|
||||
|
||||
public static Availability unavailable(String reason) {
|
||||
Assert.notNull(reason, "Reason for not being available must be provided");
|
||||
return new Availability(reason);
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return reason == null;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* http://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;
|
||||
|
||||
/**
|
||||
* Indicates that a command exists but is currently not invokable.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class CommandNotCurrentlyAvailable extends RuntimeException {
|
||||
|
||||
private final String command;
|
||||
private final Availability availability;
|
||||
|
||||
public CommandNotCurrentlyAvailable(String command, Availability availability) {
|
||||
super(String.format("Command '%s' exists but is not currently available because %s", command, availability.getReason()));
|
||||
this.command = command;
|
||||
this.availability = availability;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public Availability getAvailability() {
|
||||
return availability;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.springframework.shell;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -36,7 +37,16 @@ public class MethodTarget {
|
||||
|
||||
private final String help;
|
||||
|
||||
/**
|
||||
* If not null, returns whether or not the command is currently available. Implementations must be idempotent.
|
||||
*/
|
||||
private final Supplier<Availability> availabilityIndicator;
|
||||
|
||||
public MethodTarget(Method method, Object bean, String help) {
|
||||
this(method, bean, help, null);
|
||||
}
|
||||
|
||||
public MethodTarget(Method method, Object bean, String help, Supplier<Availability> availabilityIndicator) {
|
||||
Assert.notNull(method, "Method cannot be null");
|
||||
Assert.notNull(bean, "Bean cannot be null");
|
||||
Assert.hasText(help, String.format("Help cannot be blank when trying to define command based on '%s'", method));
|
||||
@@ -44,6 +54,7 @@ public class MethodTarget {
|
||||
this.method = method;
|
||||
this.bean = bean;
|
||||
this.help = help;
|
||||
this.availabilityIndicator = availabilityIndicator != null ? availabilityIndicator : () -> Availability.available();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,13 +62,21 @@ public class MethodTarget {
|
||||
* in case of overloaded method.
|
||||
*/
|
||||
public static MethodTarget of(String name, Object bean, String help) {
|
||||
return of(name, bean, help, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a MethodTarget for the unique method named {@literal name} on the given object. Fails with an exception
|
||||
* in case of overloaded method.
|
||||
*/
|
||||
public static MethodTarget of(String name, Object bean, String help, Supplier<Availability> availabilityIndicator) {
|
||||
Set<Method> found = new HashSet<>();
|
||||
ReflectionUtils.doWithMethods(bean.getClass(), found::add, m -> m.getName().equals(name));
|
||||
if (found.size() != 1) {
|
||||
throw new IllegalArgumentException(String.format("Could not find unique method named '%s' on object of class %s. Found %s",
|
||||
name, bean.getClass(), found));
|
||||
}
|
||||
return new MethodTarget(found.iterator().next(), bean, help);
|
||||
return new MethodTarget(found.iterator().next(), bean, help, availabilityIndicator);
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
@@ -72,6 +91,10 @@ public class MethodTarget {
|
||||
return help;
|
||||
}
|
||||
|
||||
public Availability getAvailability() {
|
||||
return availabilityIndicator.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
||||
@@ -131,16 +131,21 @@ public class Shell implements CommandRegistry {
|
||||
Object result;
|
||||
if (command != null) {
|
||||
MethodTarget methodTarget = methodTargets.get(command);
|
||||
List<String> wordsForArgs = wordsForArguments(command, words);
|
||||
Method method = methodTarget.getMethod();
|
||||
Availability availability = methodTarget.getAvailability();
|
||||
if (availability.isAvailable()) {
|
||||
List<String> wordsForArgs = wordsForArguments(command, words);
|
||||
Method method = methodTarget.getMethod();
|
||||
|
||||
try {
|
||||
Object[] args = resolveArgs(method, wordsForArgs);
|
||||
validateArgs(args, methodTarget);
|
||||
result = ReflectionUtils.invokeMethod(method, methodTarget.getBean(), args);
|
||||
}
|
||||
catch (Exception e) {
|
||||
result = e;
|
||||
try {
|
||||
Object[] args = resolveArgs(method, wordsForArgs);
|
||||
validateArgs(args, methodTarget);
|
||||
result = ReflectionUtils.invokeMethod(method, methodTarget.getBean(), args);
|
||||
}
|
||||
catch (Exception e) {
|
||||
result = e;
|
||||
}
|
||||
} else {
|
||||
result = new CommandNotCurrentlyAvailable(command, availability);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
Reference in New Issue
Block a user