Use Guice to create custom arg4j OptionHandler instances
This permits us to inject Guice managed objects into any ObjectHandler via assisted factory injection, which permits them to perform database queries during option parsing by accessing the per-request ReviewDb. Change-Id: I14a6b5296d8d77b5e3966846bbaa58f6e8ebb09a Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -15,10 +15,15 @@
|
||||
package com.google.gerrit.pgm;
|
||||
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
|
||||
import org.kohsuke.args4j.CmdLineException;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.Collections;
|
||||
|
||||
/** Base class for command line invocations of Gerrit Code Review. */
|
||||
public abstract class AbstractProgram {
|
||||
@@ -38,7 +43,8 @@ public abstract class AbstractProgram {
|
||||
}
|
||||
|
||||
public final int main(final String[] argv) throws Exception {
|
||||
final CmdLineParser clp = new CmdLineParser(this);
|
||||
final Injector empty = emptyInjector();
|
||||
final CmdLineParser clp = new CmdLineParser(empty, this);
|
||||
try {
|
||||
clp.parseArgument(argv);
|
||||
} catch (CmdLineException err) {
|
||||
@@ -64,6 +70,10 @@ public abstract class AbstractProgram {
|
||||
return run();
|
||||
}
|
||||
|
||||
private static Injector emptyInjector() {
|
||||
return Guice.createInjector(Collections.<Module> emptyList());
|
||||
}
|
||||
|
||||
/** Method that never returns, e.g. to keep a daemon running. */
|
||||
protected int never() {
|
||||
synchronized (sleepLock) {
|
||||
|
||||
@@ -34,10 +34,22 @@
|
||||
|
||||
package com.google.gerrit.pgm;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.CmdLineException;
|
||||
import org.kohsuke.args4j.IllegalAnnotationError;
|
||||
import org.kohsuke.args4j.Option;
|
||||
import org.kohsuke.args4j.OptionDef;
|
||||
import org.kohsuke.args4j.spi.OptionHandler;
|
||||
import org.kohsuke.args4j.spi.Setter;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* Extended command line parser which handles --foo=value arguments.
|
||||
@@ -47,7 +59,14 @@ import java.util.ArrayList;
|
||||
* --foo=value long option, so we convert from the GNU style format to the
|
||||
* args4j style format prior to invoking args4j for parsing.
|
||||
*/
|
||||
public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser {
|
||||
public class CmdLineParser {
|
||||
public interface Factory {
|
||||
CmdLineParser create(Object bean);
|
||||
}
|
||||
|
||||
private final Injector injector;
|
||||
private final MyParser parser;
|
||||
|
||||
/**
|
||||
* Creates a new command line owner that parses arguments/options and set them
|
||||
* into the given object.
|
||||
@@ -60,11 +79,29 @@ public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser {
|
||||
* @throws IllegalAnnotationError if the option bean class is using args4j
|
||||
* annotations incorrectly.
|
||||
*/
|
||||
public CmdLineParser(final Object bean) throws IllegalAnnotationError {
|
||||
super(bean);
|
||||
@Inject
|
||||
public CmdLineParser(final Injector injector, @Assisted final Object bean)
|
||||
throws IllegalAnnotationError {
|
||||
this.injector = injector;
|
||||
this.parser = new MyParser(bean);
|
||||
}
|
||||
|
||||
public void addArgument(Setter<?> setter, Argument a) {
|
||||
parser.addArgument(setter, a);
|
||||
}
|
||||
|
||||
public void addOption(Setter<?> setter, Option o) {
|
||||
parser.addOption(setter, o);
|
||||
}
|
||||
|
||||
public void printSingleLineUsage(Writer w, ResourceBundle rb) {
|
||||
parser.printSingleLineUsage(w, rb);
|
||||
}
|
||||
|
||||
public void printUsage(Writer out, ResourceBundle rb) {
|
||||
parser.printUsage(out, rb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseArgument(final String... args) throws CmdLineException {
|
||||
final ArrayList<String> tmp = new ArrayList<String>(args.length);
|
||||
for (int argi = 0; argi < args.length; argi++) {
|
||||
@@ -87,6 +124,47 @@ public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser {
|
||||
tmp.add(str);
|
||||
}
|
||||
|
||||
super.parseArgument(tmp.toArray(new String[tmp.size()]));
|
||||
parser.parseArgument(tmp.toArray(new String[tmp.size()]));
|
||||
}
|
||||
|
||||
private class MyParser extends org.kohsuke.args4j.CmdLineParser {
|
||||
MyParser(final Object bean) {
|
||||
super(bean);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected OptionHandler createOptionHandler(final OptionDef option,
|
||||
final Setter setter) {
|
||||
if (isHandlerSpecified(option) || isEnum(setter) || isPrimitive(setter)) {
|
||||
return super.createOptionHandler(option, setter);
|
||||
}
|
||||
|
||||
final Key<OptionHandlerFactory<?>> key =
|
||||
OptionHandlerUtil.keyFor(setter.getType());
|
||||
Injector i = injector;
|
||||
while (i != null) {
|
||||
if (i.getBindings().containsKey(key)) {
|
||||
return i.getInstance(key).create(this, option, setter);
|
||||
}
|
||||
i = i.getParent();
|
||||
}
|
||||
|
||||
return super.createOptionHandler(option, setter);
|
||||
}
|
||||
|
||||
private boolean isHandlerSpecified(final OptionDef option) {
|
||||
return option.handler() != OptionHandler.class;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean isEnum(final Setter setter) {
|
||||
return Enum.class.isAssignableFrom(setter.getType());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean isPrimitive(final Setter setter) {
|
||||
return setter.getType().isPrimitive();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2009 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.pgm;
|
||||
|
||||
import org.kohsuke.args4j.OptionDef;
|
||||
import org.kohsuke.args4j.spi.OptionHandler;
|
||||
import org.kohsuke.args4j.spi.Setter;
|
||||
|
||||
/** Creates an args4j OptionHandler through a Guice Injector. */
|
||||
public interface OptionHandlerFactory<T> {
|
||||
@SuppressWarnings("unchecked")
|
||||
OptionHandler create(org.kohsuke.args4j.CmdLineParser cmdLineParser,
|
||||
OptionDef optionDef, Setter setter);
|
||||
}
|
||||
36
src/main/java/com/google/gerrit/pgm/OptionHandlerUtil.java
Normal file
36
src/main/java/com/google/gerrit/pgm/OptionHandlerUtil.java
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2009 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.pgm;
|
||||
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.internal.MoreTypes.ParameterizedTypeImpl;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/** Utilities to support creating OptionHandler instances. */
|
||||
public class OptionHandlerUtil {
|
||||
/** Generate a key for an {@link OptionHandlerFactory} in Guice. */
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Key<OptionHandlerFactory<T>> keyFor(final Class<T> valueType) {
|
||||
final Type factoryType =
|
||||
new ParameterizedTypeImpl(null, OptionHandlerFactory.class, valueType);
|
||||
|
||||
return (Key<OptionHandlerFactory<T>>) Key.get(TypeLiteral.get(factoryType));
|
||||
}
|
||||
|
||||
private OptionHandlerUtil() {
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,9 @@ public abstract class BaseCommand implements Command {
|
||||
|
||||
private ExitCallback exit;
|
||||
|
||||
@Inject
|
||||
private CmdLineParser.Factory cmdLineParserFactory;
|
||||
|
||||
@Inject
|
||||
private RequestCleanup cleanup;
|
||||
|
||||
@@ -180,7 +183,7 @@ public abstract class BaseCommand implements Command {
|
||||
|
||||
/** Construct a new parser for this command's received command line. */
|
||||
protected CmdLineParser newCmdLineParser() {
|
||||
return new CmdLineParser(this);
|
||||
return cmdLineParserFactory.create(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,13 +16,21 @@ package com.google.gerrit.server.ssh;
|
||||
|
||||
import static com.google.inject.Scopes.SINGLETON;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.PatchSet;
|
||||
import com.google.gerrit.pgm.CmdLineParser;
|
||||
import com.google.gerrit.pgm.OptionHandlerFactory;
|
||||
import com.google.gerrit.pgm.OptionHandlerUtil;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.RemotePeer;
|
||||
import com.google.gerrit.server.config.FactoryModule;
|
||||
import com.google.gerrit.server.config.GerritRequestModule;
|
||||
import com.google.gerrit.server.ssh.commands.DefaultCommandModule;
|
||||
import com.google.gerrit.server.ssh.commands.PatchSetIdHandler;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.assistedinject.FactoryProvider;
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
import com.google.inject.servlet.SessionScoped;
|
||||
|
||||
@@ -31,6 +39,7 @@ import org.apache.sshd.common.session.AbstractSession;
|
||||
import org.apache.sshd.server.CommandFactory;
|
||||
import org.apache.sshd.server.PublickeyAuthenticator;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.kohsuke.args4j.spi.OptionHandler;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
@@ -45,6 +54,7 @@ public class SshModule extends FactoryModule {
|
||||
|
||||
configureSessionScope();
|
||||
configureRequestScope();
|
||||
configureCmdLineParser();
|
||||
|
||||
bind(SshInfo.class).to(SshDaemon.class).in(SINGLETON);
|
||||
factory(DispatchCommand.Factory.class);
|
||||
@@ -85,4 +95,23 @@ public class SshModule extends FactoryModule {
|
||||
SshScopes.REQUEST);
|
||||
bind(CurrentUser.class).to(IdentifiedUser.class);
|
||||
}
|
||||
|
||||
private void configureCmdLineParser() {
|
||||
factory(CmdLineParser.Factory.class);
|
||||
|
||||
registerOptionHandler(PatchSet.Id.class, PatchSetIdHandler.class);
|
||||
}
|
||||
|
||||
private <T> void registerOptionHandler(Class<T> type,
|
||||
Class<? extends OptionHandler<T>> impl) {
|
||||
final Key<OptionHandlerFactory<T>> key = OptionHandlerUtil.keyFor(type);
|
||||
|
||||
final TypeLiteral<OptionHandlerFactory<T>> factoryType =
|
||||
new TypeLiteral<OptionHandlerFactory<T>>() {};
|
||||
|
||||
final TypeLiteral<? extends OptionHandler<T>> implType =
|
||||
TypeLiteral.get(impl);
|
||||
|
||||
bind(key).toProvider(FactoryProvider.newFactory(factoryType, implType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,13 +46,9 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ApproveCommand extends BaseCommand {
|
||||
static {
|
||||
CmdLineParser.registerHandler(PatchSet.Id.class, PatchSetIdHandler.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final CmdLineParser newCmdLineParser() {
|
||||
final CmdLineParser parser = new CmdLineParser(this);
|
||||
final CmdLineParser parser = super.newCmdLineParser();
|
||||
for (CmdOption c : optionList) {
|
||||
parser.addOption(c, c);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package com.google.gerrit.server.ssh.commands;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.PatchSet;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import org.kohsuke.args4j.CmdLineException;
|
||||
import org.kohsuke.args4j.CmdLineParser;
|
||||
@@ -24,8 +26,10 @@ import org.kohsuke.args4j.spi.Parameters;
|
||||
import org.kohsuke.args4j.spi.Setter;
|
||||
|
||||
public class PatchSetIdHandler extends OptionHandler<PatchSet.Id> {
|
||||
public PatchSetIdHandler(final CmdLineParser parser, final OptionDef option,
|
||||
final Setter<? super PatchSet.Id> setter) {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Inject
|
||||
public PatchSetIdHandler(@Assisted final CmdLineParser parser,
|
||||
@Assisted final OptionDef option, @Assisted final Setter setter) {
|
||||
super(parser, option, setter);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user