提交 5167a644 编写于 作者: K kohsuke

added a mechanism to perform a method parameter binding for CLI, which is...

added a mechanism to perform a method parameter binding for CLI, which is somewhat akin to how Stapler performs a method parameter binding for HTTP requests.

This makes it much easier to expose CLI commands from existing model objects.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@21448 71c3de6d-444a-0410-be80-ed276b4c234a
上级 c03af5e9
......@@ -338,7 +338,7 @@ THE SOFTWARE.
<dependency>
<groupId>args4j</groupId>
<artifactId>args4j</artifactId>
<version>2.0.13</version>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>org.jvnet.hudson</groupId>
......
package hudson.cli;
import hudson.Extension;
import hudson.model.AbstractProject;
import org.kohsuke.args4j.Argument;
/**
* Disables a job.
*
* @author Kohsuke Kawaguchi
*/
@Extension
public class DisableJobCommand extends CLICommand {
@Argument
public AbstractProject src;
public String getShortDescription() {
return "Disables a job";
}
protected int run() throws Exception {
src.makeDisabled(true);
return 0;
}
}
package hudson.cli;
import hudson.Extension;
import hudson.model.AbstractProject;
import org.kohsuke.args4j.Argument;
/**
* Enables a job.
*
* @author Kohsuke Kawaguchi
*/
@Extension
public class EnableJobCommand extends CLICommand {
@Argument
public AbstractProject src;
public String getShortDescription() {
return "Enables a previously disabled job";
}
protected int run() throws Exception {
src.makeDisabled(false);
return 0;
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.cli.declarative;
import hudson.Extension;
......@@ -8,8 +31,12 @@ import hudson.cli.CloneableCLICommand;
import hudson.model.Hudson;
import org.jvnet.hudson.annotation_indexer.Index;
import org.jvnet.localizer.ResourceBundleHolder;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.CmdLineException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
......@@ -17,6 +44,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import static java.util.logging.Level.SEVERE;
import java.util.logging.Logger;
......@@ -34,6 +62,18 @@ public class CLIRegisterer extends ExtensionFinder {
return Collections.emptyList();
}
/**
* Finds a resolved method annotated with {@link CLIResolver}.
*/
private Method findResolver(Class type) throws IOException {
List<Method> resolvers = Util.filter(Index.list(CLIResolver.class, Hudson.getInstance().getPluginManager().uberClassLoader), Method.class);
for ( ; type!=null; type=type.getSuperclass())
for (Method m : resolvers)
if (m.getReturnType()==type)
return m;
return null;
}
private List<CLICommand> discover(final Hudson hudson) {
LOGGER.fine("Listing up @CLIMethod");
List<CLICommand> r = new ArrayList<CLICommand>();
......@@ -55,30 +95,50 @@ public class CLIRegisterer extends ExtensionFinder {
public String getShortDescription() {
// format by using the right locale
return res.format(name+".shortDescription");
return res.format("CLI."+name+".shortDescription");
}
protected int run() throws Exception {
@Override
public int main(List<String> args, Locale locale, InputStream stdin, PrintStream stdout, PrintStream stderr) {
CmdLineParser parser = new CmdLineParser(null);
try {
m.invoke(resolve(m.getDeclaringClass()));
return 0;
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof Exception)
throw (Exception) t;
throw e;
try {
MethodBinder resolver = null;
if (!Modifier.isStatic(m.getModifiers())) {
// decide which instance receives the method call
Method r = findResolver(m.getDeclaringClass());
if (r==null) {
stderr.println("Unable to find the resolver method annotated with @CLIResolver for "+m.getReturnType());
return 1;
}
resolver = new MethodBinder(r,parser);
}
MethodBinder invoker = new MethodBinder(m, parser);
parser.parseArgument(args);
Object instance = resolver==null ? null : resolver.call(null);
invoker.call(instance);
return 0;
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof Exception)
throw (Exception) t;
throw e;
}
} catch (CmdLineException e) {
stderr.println(e.getMessage());
printUsage(stderr,parser);
return 1;
} catch (Exception e) {
e.printStackTrace(stderr);
return 1;
}
}
/**
* Finds an instance to invoke a CLI method on.
*/
private Object resolve(Class type) {
if (Modifier.isStatic(m.getModifiers()))
return null;
// TODO: support resolver
return hudson;
protected int run() throws Exception {
throw new UnsupportedOperationException();
}
});
} catch (ClassNotFoundException e) {
......
......@@ -21,24 +21,33 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.cli;
package hudson.cli.declarative;
import hudson.Extension;
import hudson.model.Hudson;
import hudson.cli.CLICommand;
import org.jvnet.hudson.annotation_indexer.Indexed;
import org.kohsuke.args4j.CmdLineException;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
/**
* Clears the job queue.
* Annotates a resolver method that binds a portion of the command line arguments and parameters
* to an instance whose {@link CLIMethod} is invoked for the final processing.
*
* <p>
* The resolver method shall never return null --- it should instead indicate a failure by throwing
* {@link CmdLineException}.
*
* @author Kohsuke Kawaguchi
* @see CLICommand
* @since 1.321
*/
@Extension
public class ClearQueueCommand extends CLICommand {
public String getShortDescription() {
return "Clears the job queue";
}
protected int run() throws Exception {
Hudson.getInstance().getQueue().clear();
return 0;
}
@Indexed
@Retention(RUNTIME)
@Target({METHOD})
@Documented
public @interface CLIResolver {
}
......@@ -21,29 +21,77 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.cli;
package hudson.cli.declarative;
import hudson.model.TopLevelItem;
import hudson.Extension;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.spi.Setter;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
/**
* Deletes a job.
* Binds method parameters to CLI arguments and parameters via args4j.
* Once the parser fills in the instance state, {@link #call(Object)}
* can be used to invoke a method.
*
* @author Kohsuke Kawaguchi
*/
@Extension
public class DeleteJobCommand extends CLICommand {
@Override
public String getShortDescription() {
return "Deletes a job";
}
class MethodBinder {
private final Method method;
private final Object[] arguments;
/**
* @param method
*/
public MethodBinder(Method method, CmdLineParser parser) {
this.method = method;
@Argument
public TopLevelItem job;
Type[] params = method.getGenericParameterTypes();
final Class[] paramTypes = method.getParameterTypes();
arguments = new Object[params.length];
protected int run() throws Exception {
job.delete();
return 0;
Annotation[][] pa = method.getParameterAnnotations();
for (int i=0; i<params.length; i++) {
final int index = i;
for (Annotation a : pa[i]) {
// TODO: collection and map support
Setter setter = new Setter() {
public void addValue(Object value) throws CmdLineException {
arguments[index] = value;
}
public Class getType() {
return paramTypes[index];
}
public boolean isMultiValued() {
return false;
}
};
if (a instanceof Option) {
parser.addOption(setter,(Option)a);
}
if (a instanceof Argument) {
parser.addArgument(setter,(Argument)a);
}
}
}
}
}
public Object call(Object instance) throws Exception {
try {
return method.invoke(instance,arguments);
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof Exception)
throw (Exception) t;
throw e;
}
}
}
......@@ -27,8 +27,8 @@ import hudson.XmlFile;
import hudson.Util;
import hudson.Functions;
import hudson.BulkChange;
import hudson.DescriptorExtensionList;
import hudson.util.Iterators;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.security.AccessControlled;
import hudson.security.Permission;
import hudson.security.ACL;
......@@ -43,9 +43,10 @@ import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.HttpDeletable;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import javax.servlet.ServletException;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
/**
* Partial default implementation of {@link Item}.
......@@ -259,6 +260,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
/**
* Deletes this item.
*/
@CLIMethod(name="delete-job")
public synchronized void delete() throws IOException, InterruptedException {
performDelete();
......@@ -278,4 +280,16 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
public String toString() {
return super.toString()+'['+getFullName()+']';
}
/**
* Used for CLI binding.
*/
@CLIResolver
public static AbstractItem resolveForCLI(
@Argument(required=true,metaVar="NAME",usage="Job name") String name) throws CmdLineException {
AbstractItem item = Hudson.getInstance().getItemByFullName(name, AbstractItem.class);
if (item==null)
throw new CmdLineException(null,Messages.AbstractItem_NoSuchJobExists(name,AbstractProject.findNearest(name).getFullName()));
return item;
}
}
......@@ -28,6 +28,8 @@ import hudson.FeedAdapter;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.cli.declarative.CLIResolver;
import hudson.cli.declarative.CLIMethod;
import hudson.slaves.WorkspaceList;
import hudson.model.Cause.LegacyCodeCause;
import hudson.model.Cause.UserCause;
......@@ -66,6 +68,7 @@ import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.args4j.Argument;
import javax.servlet.ServletException;
......@@ -449,6 +452,16 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
save();
}
@CLIMethod(name="disable-job")
public void disable() throws IOException {
makeDisabled(true);
}
@CLIMethod(name="enable-job")
public void enable() throws IOException {
makeDisabled(false);
}
@Override
public BallColor getIconColor() {
if(isDisabled())
......
......@@ -28,6 +28,8 @@ import hudson.ExtensionList;
import hudson.ExtensionPoint;
import hudson.Util;
import hudson.XmlFile;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.remoting.AsyncFutureImpl;
import hudson.model.AbstractProject;
import hudson.model.Node.Mode;
......@@ -298,6 +300,7 @@ public class Queue extends ResourceController implements Saveable {
/**
* Wipes out all the items currently in the queue, as if all of them are cancelled at once.
*/
@CLIMethod(name="clear-queue")
public synchronized void clear() {
for (WaitingItem i : waitingList)
i.onCancelled();
......@@ -1565,4 +1568,9 @@ public class Queue extends ResourceController implements Saveable {
clear();
}
}
@CLIResolver
public static Queue getInstance() {
return Hudson.getInstance().getQueue();
}
}
......@@ -24,6 +24,7 @@ AbstractBuild.BuildingRemotely=Building remotely on {0}
AbstractBuild.BuildingOnMaster=Building on master
AbstractBuild.KeptBecause=kept because of {0}
AbstractItem.NoSuchJobExists=No such job ''{0}'' exists. Perhaps you meant ''{1}''?
AbstractProject.NewBuildForWorkspace=Scheduling a new build to get a workspace.
AbstractProject.Pronoun=Project
AbstractProject.Aborted=Aborted
......@@ -55,6 +56,11 @@ BallColor.Pending=Pending
BallColor.Success=Success
BallColor.Unstable=Unstable
CLI.clear-queue.shortDescription=Clears the build queue
CLI.delete-job.shortDescription=Deletes a job
CLI.disable-job.shortDescription=Disables a job
CLI.enable-job.shortDescription=Enables a job
Computer.Caption=Slave {0}
Computer.Permissions.Title=Slave
Computer.ConfigurePermission.Description=This permission allows users to configure slaves.
......
......@@ -125,7 +125,7 @@ THE SOFTWARE.
<dependency>
<groupId>args4j</groupId>
<artifactId>args4j</artifactId>
<version>2.0.13</version>
<version>2.0.16</version>
<scope>provided</scope>
</dependency>
<dependency>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册