/******************************************************************************* * * Copyright (c) 2004-2012, Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Kohsuke Kawaguchi, Winston Prakash * *******************************************************************************/ package hudson.cli.declarative; import hudson.Extension; import hudson.ExtensionComponent; import hudson.ExtensionFinder; import hudson.Util; import hudson.cli.CLICommand; import hudson.cli.CloneableCLICommand; import hudson.model.Hudson; import hudson.remoting.Channel; import hudson.security.CliAuthenticator; import org.jvnet.hudson.annotation_indexer.Index; import org.jvnet.localizer.ResourceBundleHolder; import org.kohsuke.args4j.ClassParser; 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; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Stack; import static java.util.logging.Level.SEVERE; import java.util.logging.Logger; import org.eclipse.hudson.security.HudsonSecurityEntitiesHolder; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; /** * Discover {@link CLIMethod}s and register them as {@link CLICommand} * implementations. * * @author Kohsuke Kawaguchi */ @Extension public class CLIRegisterer extends ExtensionFinder { public <T> Collection<ExtensionComponent<T>> find(Class<T> type, Hudson hudson) { if (type == CLICommand.class) { return (List) discover(hudson); } else { 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<ExtensionComponent<CLICommand>> discover(final Hudson hudson) { LOGGER.fine("Listing up @CLIMethod"); List<ExtensionComponent<CLICommand>> r = new ArrayList<ExtensionComponent<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("CLI." + name + ".shortDescription"); // make sure we have the resource, to fail early r.add(new ExtensionComponent<CLICommand>(new CloneableCLICommand() { @Override public String getName() { return name; } public String getShortDescription() { // format by using the right locale return res.format("CLI." + name + ".shortDescription"); } @Override public int main(List<String> args, Locale locale, InputStream stdin, PrintStream stdout, PrintStream stderr) { this.stdout = stdout; this.stderr = stderr; this.locale = locale; this.channel = Channel.current(); registerOptionHandlers(); CmdLineParser parser = new CmdLineParser(null); try { SecurityContext sc = SecurityContextHolder.getContext(); Authentication old = sc.getAuthentication(); try { // build up the call sequence Stack<Method> chains = new Stack<Method>(); Method method = m; while (true) { chains.push(method); if (Modifier.isStatic(method.getModifiers())) { break; // the chain is complete. } // the method in question is an instance method, so we need to resolve the instance by using another resolver Class<?> type = method.getDeclaringClass(); method = findResolver(type); if (method == null) { stderr.println("Unable to find the resolver method annotated with @CLIResolver for " + type); return 1; } } List<MethodBinder> binders = new ArrayList<MethodBinder>(); while (!chains.isEmpty()) { binders.add(new MethodBinder(chains.pop(), parser)); } // authentication CliAuthenticator authenticator = HudsonSecurityEntitiesHolder.getHudsonSecurityManager().getSecurityRealm().createCliAuthenticator(this); new ClassParser().parse(authenticator, parser); // fill up all the binders parser.parseArgument(args); Authentication auth = authenticator.authenticate(); if (auth == Hudson.ANONYMOUS) { auth = loadStoredAuthentication(); } sc.setAuthentication(auth); // run the CLI with the right credential hudson.checkPermission(Hudson.READ); // resolve them Object instance = null; for (MethodBinder binder : binders) { instance = binder.call(instance); } if (instance instanceof Integer) { return (Integer) instance; } else { return 0; } } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); if (t instanceof Exception) { throw (Exception) t; } throw e; } finally { sc.setAuthentication(old); // restore } } catch (CmdLineException e) { stderr.println(e.getMessage()); printUsage(stderr, parser); return 1; } catch (Exception e) { e.printStackTrace(stderr); return 1; } } protected int run() throws Exception { throw new UnsupportedOperationException(); } })); } 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()); }