提交 63cf2828 编写于 作者: K kohsuke

- Added declarative way of writing a command.

- Added <tt>restart</tt> CLI command.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@20860 71c3de6d-444a-0410-be80-ed276b4c234a
上级 f85f13c5
......@@ -340,6 +340,11 @@ THE SOFTWARE.
<artifactId>args4j</artifactId>
<version>2.0.13</version>
</dependency>
<dependency>
<groupId>org.jvnet.hudson</groupId>
<artifactId>annotation-indexer</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jvnet.localizer</groupId>
<artifactId>localizer</artifactId>
......
......@@ -23,26 +23,21 @@
*/
package hudson.cli;
import hudson.ExtensionPoint;
import hudson.AbortException;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.AbortException;
import hudson.remoting.Channel;
import hudson.remoting.Callable;
import hudson.ExtensionPoint;
import hudson.cli.declarative.CLIMethod;
import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson;
import hudson.model.Hudson;
import org.kohsuke.args4j.CmdLineParser;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.spi.OptionHandler;
import org.jvnet.tiger_types.Types;
import org.apache.commons.discovery.resource.classes.DiscoverClasses;
import org.apache.commons.discovery.resource.ClassLoaders;
import org.apache.commons.discovery.resource.names.DiscoverServiceNames;
import org.apache.commons.discovery.ResourceClassIterator;
import org.apache.commons.discovery.ResourceNameIterator;
import org.kohsuke.args4j.CmdLineParser;
import java.io.PrintStream;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.List;
import java.util.Locale;
......@@ -58,7 +53,7 @@ import java.util.Locale;
*
* <p>
* The Hudson master then picks the right {@link CLICommand} to execute, clone it, and
* calls {@link #main(List, InputStream, PrintStream, PrintStream)} method.
* calls {@link #main(List, Locale, InputStream, PrintStream, PrintStream)} method.
*
* <h2>Note for CLI command implementor</h2>
* <ul>
......@@ -68,7 +63,7 @@ import java.util.Locale;
* <li>
* Use <a href="http://args4j.dev.java.net/">args4j</a> annotation on your implementation to define
* options and arguments (however, if you don't like that, you could override
* the {@link #main(List, InputStream, PrintStream, PrintStream)} method directly.
* the {@link #main(List, Locale, InputStream, PrintStream, PrintStream)} method directly.
*
* <li>
* stdin, stdout, stderr are remoted, so proper buffering is necessary for good user experience.
......@@ -81,7 +76,9 @@ import java.util.Locale;
*
* @author Kohsuke Kawaguchi
* @since 1.302
* @see CLIMethod
*/
@LegacyInstancesAreScopedToHudson
public abstract class CLICommand implements ExtensionPoint, Cloneable {
/**
* Connected to stdout and stderr of the CLI agent that initiated the session.
......@@ -108,6 +105,11 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
*/
protected transient Channel channel;
/**
* The locale of the client. Messages should be formatted with this resource.
*/
protected transient Locale locale;
/**
* Gets the command name.
......@@ -137,10 +139,11 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
*/
public abstract String getShortDescription();
public int main(List<String> args, InputStream stdin, PrintStream stdout, PrintStream stderr) {
public int main(List<String> args, Locale locale, InputStream stdin, PrintStream stdout, PrintStream stderr) {
this.stdin = new BufferedInputStream(stdin);
this.stdout = stdout;
this.stderr = stderr;
this.locale = locale;
this.channel = Channel.current();
CmdLineParser p = new CmdLineParser(this);
try {
......@@ -179,6 +182,19 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
p.printUsage(stderr);
}
/**
* Creates a clone to be used to execute a command.
*/
protected CLICommand createClone() {
try {
return getClass().newInstance();
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} catch (InstantiationException e) {
throw new AssertionError(e);
}
}
/**
* Returns all the registered {@link CLICommand}s.
*/
......@@ -190,17 +206,9 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
* Obtains a copy of the command for invocation.
*/
public static CLICommand clone(String name) {
for (CLICommand cmd : all()) {
if(name.equals(cmd.getName())) {
try {
return cmd.getClass().newInstance();
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} catch (InstantiationException e) {
throw new AssertionError(e);
}
}
}
for (CLICommand cmd : all())
if(name.equals(cmd.getName()))
return cmd.createClone();
return null;
}
}
......@@ -75,14 +75,14 @@ public class CliManagerImpl implements CliEntryPoint, Serializable {
SecurityContextHolder.getContext().setAuthentication(auth);
try {
// execute the command, do so with the originator of the request as the principal
return cmd.main(args.subList(1,args.size()),stdin, out, err);
return cmd.main(args.subList(1,args.size()),locale, stdin, out, err);
} finally {
SecurityContextHolder.getContext().setAuthentication(old);
}
}
err.println("No such command: "+subCmd);
new HelpCommand().main(Collections.<String>emptyList(), stdin, out, err);
new HelpCommand().main(Collections.<String>emptyList(), locale, stdin, out, err);
return -1;
}
......
package hudson.cli;
/**
* {@link Cloneable} {@link CLICommand}.
*
* Uses {@link #clone()} instead of "new" to create a copy for exection.
*
* @author Kohsuke Kawaguchi
*/
public abstract class CloneableCLICommand extends CLICommand implements Cloneable {
@Override
protected CLICommand createClone() {
try {
return (CLICommand)clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}
......@@ -29,6 +29,7 @@ import org.codehaus.groovy.tools.shell.Groovysh;
import org.codehaus.groovy.tools.shell.IO;
import java.util.List;
import java.util.Locale;
import java.io.PrintStream;
import java.io.InputStream;
import java.io.BufferedInputStream;
......@@ -50,7 +51,7 @@ public class GroovyshCommand extends CLICommand {
}
@Override
public int main(List<String> args, InputStream stdin, PrintStream stdout, PrintStream stderr) {
public int main(List<String> args, Locale locale, InputStream stdin, PrintStream stdout, PrintStream stderr) {
// this allows the caller to manipulate the JVM state, so require the admin privilege.
Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
......
/*
* 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.cli.CLICommand;
import org.jvnet.hudson.annotation_indexer.Indexed;
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;
/**
* Annotates methods on model objects to expose them as CLI commands.
*
* <p>
* You need to have <tt>Messages.properties</tt> in the same package with the
* <tt>command-name.shortDescription</tt> key to describe the command.
* This is used for {@link CLICommand#getShortDescription()}.
*
* @author Kohsuke Kawaguchi
* @see CLICommand
* @since 1.321
*/
@Indexed
@Retention(RUNTIME)
@Target({METHOD})
@Documented
public @interface CLIMethod {
/**
* CLI command name. Used as {@link CLICommand#getName()}
*/
String name();
}
package hudson.cli.declarative;
import hudson.Extension;
import hudson.ExtensionFinder;
import hudson.Util;
import hudson.cli.CLICommand;
import hudson.cli.CloneableCLICommand;
import hudson.model.Hudson;
import org.jvnet.hudson.annotation_indexer.Index;
import org.jvnet.localizer.ResourceBundleHolder;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static java.util.logging.Level.SEVERE;
import java.util.logging.Logger;
/**
* Discover {@link CLIMethod}s and register them as {@link CLICommand} implementations.
*
* @author Kohsuke Kawaguchi
*/
@Extension
public class CLIRegisterer extends ExtensionFinder {
public <T> Collection<T> findExtensions(Class<T> type, Hudson hudson) {
if (type==CLICommand.class)
return (List<T>)discover(hudson);
else
return Collections.emptyList();
}
private List<CLICommand> discover(final Hudson hudson) {
LOGGER.fine("Listing up @CLIMethod");
List<CLICommand> r = new ArrayList<CLICommand>();
try {
for ( final Method m : Util.filter(Index.list(CLIMethod.class, hudson.getPluginManager().uberClassLoader),Method.class)) {
try {
// command name
final String name = m.getAnnotation(CLIMethod.class).name();
final ResourceBundleHolder res = loadMessageBundle(m);
res.format(name+".shortDescription"); // make sure we have the resource, to fail early
r.add(new CloneableCLICommand() {
@Override
public String getName() {
return name;
}
public String getShortDescription() {
// format by using the right locale
return res.format(name+".shortDescription");
}
protected int run() throws Exception {
try {
m.invoke(resolve(m.getDeclaringClass()));
return 0;
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof Exception)
throw (Exception) t;
throw e;
}
}
/**
* 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;
}
});
} catch (ClassNotFoundException e) {
LOGGER.log(SEVERE,"Failed to process @CLIMethod: "+m,e);
}
}
} catch (IOException e) {
LOGGER.log(SEVERE, "Failed to discvoer @CLIMethod",e);
}
return r;
}
/**
* Locates the {@link ResourceBundleHolder} for this CLI method.
*/
private ResourceBundleHolder loadMessageBundle(Method m) throws ClassNotFoundException {
Class c = m.getDeclaringClass();
Class<?> msg = c.getClassLoader().loadClass(c.getName().substring(0, c.getName().lastIndexOf(".")) + ".Messages");
return ResourceBundleHolder.get(msg);
}
private static final Logger LOGGER = Logger.getLogger(CLIRegisterer.class.getName());
}
/*
* 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.
*/
/**
* Code for supporting declarative CLI commands, which are annotated methods on model objects.
*/
package hudson.cli.declarative;
\ No newline at end of file
......@@ -50,6 +50,7 @@ import hudson.tools.ToolInstallation;
import hudson.tools.ToolDescriptor;
import hudson.cli.CliEntryPoint;
import hudson.cli.CliManagerImpl;
import hudson.cli.declarative.CLIMethod;
import hudson.logging.LogRecorderManager;
import hudson.lifecycle.Lifecycle;
import hudson.model.Descriptor.FormException;
......@@ -192,10 +193,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import static java.util.logging.Level.SEVERE;
import java.util.regex.Pattern;
import java.nio.charset.Charset;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.crypto.SecretKey;
import groovy.lang.GroovyShell;
......@@ -544,7 +545,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
try {
proxy = ProxyConfiguration.load();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to load proxy configuration", e);
LOGGER.log(SEVERE, "Failed to load proxy configuration", e);
}
// load plugins.
......@@ -617,7 +618,24 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
FileUtils.writeStringToFile(new File(userContentDir,"readme.txt"),Messages.Hudson_USER_CONTENT_README());
}
Trigger.init(); // start running trigger
Trigger.init();
// pending SEZPOZ-8
// // invoke post initialization methods
// for ( IndexItem<PostInit,Void> i : Index.load(PostInit.class, Void.class, pluginManager.uberClassLoader)) {
// try {
// Method m = (Method)i.element();
// if (Modifier.isStatic(m.getModifiers()))
// m.invoke(null);
// else
// LOGGER.severe(m+" is annotated with @PostInit but it's not a static method");
// } catch (InstantiationException e) {
// LOGGER.log(SEVERE,"Failed to invoke @PostInit: "+i,e);
// } catch (IllegalAccessException e) {
// LOGGER.log(SEVERE,"Failed to invoke @PostInit: "+i,e);
// } catch (InvocationTargetException e) {
// LOGGER.log(SEVERE,"Failed to invoke @PostInit: "+i,e);
// }
// }
} finally {
SecurityContextHolder.clearContext();
}
......@@ -2655,7 +2673,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
User.reload();
context.setAttribute("app",Hudson.this);
} catch (IOException e) {
LOGGER.log(Level.SEVERE,"Failed to reload Hudson config",e);
LOGGER.log(SEVERE,"Failed to reload Hudson config",e);
}
}
}.start();
......@@ -2789,6 +2807,7 @@ public final class Hudson extends Node implements ItemGroup<TopLevelItem>, Stapl
/**
* Performs a restart.
*/
@CLIMethod(name="restart")
public void restart() {
final Lifecycle lifecycle = Lifecycle.get();
if(!lifecycle.canRestart())
......
......@@ -216,3 +216,5 @@ ProxyView.DisplayName=Include a global view
MyViewsProperty.ViewExistsCheck.NotExist=A view with name {0} does not exist
MyViewsProperty.ViewExistsCheck.AlreadyExists=A view with name {0} already exists
restart.shortDescription=Restart Hudson
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册