Allow REST endpoints and SSH commands to accept and handle unknown options

If a user specifies an unknown option, parsing the command-line options
fails and the user gets an error. With this change REST endpoints / SSH
commands can be more tolerant by implementing the new
UnknownOptionHandler interface. Implementors of this interface get a
callback when an unknown option is found and they can decide whether
they want to accept and handle it. If accepted, the unknown option
doesn't cause the parsing of the command-line options to fail and the
user request can succeed.

A possible use case for this is to ignore plugin options, while the
plugin is not installed (see follow-up change).

Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: I4590b2fdd90052c62906e91c457ec4dea64b6ccf
This commit is contained in:
Edwin Kempin
2019-07-10 13:44:02 +02:00
parent e3137d7330
commit 35b37e19eb
4 changed files with 152 additions and 11 deletions

View File

@@ -325,26 +325,32 @@ public class CmdLineParser {
public void parseOptionMap(ListMultimap<String, String> params) throws CmdLineException {
logger.atFinest().log("Command-line parameters: %s", params.keySet());
List<String> tmp = Lists.newArrayListWithCapacity(2 * params.size());
List<String> knownArgs = Lists.newArrayListWithCapacity(2 * params.size());
for (String key : params.keySet()) {
String name = makeOption(key);
if (isBoolean(name)) {
boolean on = false;
for (String value : params.get(key)) {
on = toBoolean(key, value);
}
if (on) {
tmp.add(name);
if (isKnownOption(name)) {
if (isBoolean(name)) {
boolean on = false;
for (String value : params.get(key)) {
on = toBoolean(key, value);
}
if (on) {
knownArgs.add(name);
}
} else {
for (String value : params.get(key)) {
knownArgs.add(name);
knownArgs.add(value);
}
}
} else {
for (String value : params.get(key)) {
tmp.add(name);
tmp.add(value);
parser.handleUnknownOption(name, value);
}
}
}
parser.parseArgument(tmp.toArray(new String[tmp.size()]));
parser.parseArgument(knownArgs.toArray(new String[knownArgs.size()]));
}
public boolean isBoolean(String name) {
@@ -370,6 +376,10 @@ public class CmdLineParser {
return name;
}
private boolean isKnownOption(String name) {
return findHandler(name) != null;
}
@SuppressWarnings("rawtypes")
private OptionHandler findHandler(String name) {
if (options == null) {
@@ -437,6 +447,8 @@ public class CmdLineParser {
}
public class MyParser extends org.kohsuke.args4j.CmdLineParser {
private final Object bean;
boolean help;
@SuppressWarnings("rawtypes")
@@ -464,11 +476,22 @@ public class CmdLineParser {
MyParser(Object bean) {
super(bean, ParserProperties.defaults().withAtSyntax(false));
this.bean = bean;
parseAdditionalOptions(bean, new HashSet<>());
addOptionsWithMetRequirements();
ensureOptionsInitialized();
}
public void handleUnknownOption(String name, String value) throws CmdLineException {
if (bean instanceof UnknownOptionHandler
&& ((UnknownOptionHandler) bean).accept(name, Strings.emptyToNull(value))) {
return;
}
// Parse argument to trigger a CmdLineException for the unknown option.
parseArgument(name, value);
}
public int addOptionsWithMetRequirements() {
int count = 0;
for (Iterator<Map.Entry<String, QueuedOption>> it = queuedOptionsByName.entrySet().iterator();

View File

@@ -0,0 +1,42 @@
// Copyright (C) 2019 The Android Open Source Project
//
// 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 com.google.gerrit.util.cli;
import com.google.gerrit.common.Nullable;
/**
* Classes that define command-line options by using the {@link org.kohsuke.args4j.Option}
* annotation can implement this class to accept and handle unknown options.
*
* <p>If a user specifies an unknown option and this unknown option doesn't get accepted, the
* parsing of the command-line options fails and the user gets an error (this is the default
* behavior if classes do not implement this interface).
*/
public interface UnknownOptionHandler {
/**
* Whether an unknown option should be accepted.
*
* <p>If an unknown option is not accepted, the parsing of the command-line options fails and the
* user gets an error.
*
* <p>This method can be used to ignore unknown options (without failure for the user) or to
* handle them.
*
* @param name the name of an unknown option that was provided by the user
* @param value the value of the unknown option that was provided by the user
* @return whether this unknown options is accepted
*/
boolean accept(String name, @Nullable String value);
}