package hudson.init; import com.google.inject.Injector; import hudson.model.Hudson; import jenkins.model.Jenkins; import org.jvnet.hudson.annotation_indexer.Index; import org.jvnet.hudson.reactor.Milestone; import org.jvnet.hudson.reactor.MilestoneImpl; import org.jvnet.hudson.reactor.Reactor; import org.jvnet.hudson.reactor.Task; import org.jvnet.hudson.reactor.TaskBuilder; import org.jvnet.localizer.ResourceBundleHolder; import java.io.IOException; import java.lang.annotation.Annotation; 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.HashSet; import java.util.List; import java.util.MissingResourceException; import java.util.Set; import java.util.logging.Logger; import static java.util.logging.Level.WARNING; /** * @author Kohsuke Kawaguchi */ abstract class TaskMethodFinder<T extends Annotation> extends TaskBuilder { private static final Logger LOGGER = Logger.getLogger(TaskMethodFinder.class.getName()); protected final ClassLoader cl; private final Set<Method> discovered = new HashSet<Method>(); private final Class<T> type; private final Class<? extends Enum> milestoneType; public TaskMethodFinder(Class<T> type, Class<? extends Enum> milestoneType, ClassLoader cl) { this.type = type; this.milestoneType = milestoneType; this.cl = cl; } // working around the restriction that Java doesn't allow annotation types to extend interfaces protected abstract String displayNameOf(T i); protected abstract String[] requiresOf(T i); protected abstract String[] attainsOf(T i); protected abstract Milestone afterOf(T i); protected abstract Milestone beforeOf(T i); protected abstract boolean fatalOf(T i); public Collection<Task> discoverTasks(Reactor session) throws IOException { List<Task> result = new ArrayList<Task>(); for (Method e : Index.list(type, cl, Method.class)) { if (filter(e)) continue; // already reported once T i = e.getAnnotation(type); if (i==null) continue; // stale index result.add(new TaskImpl(i, e)); } return result; } /** * Return true to ignore this method. */ protected boolean filter(Method e) { return !discovered.add(e); } /** * Obtains the display name of the given initialization task */ protected String getDisplayNameOf(Method e, T i) { Class<?> c = e.getDeclaringClass(); String key = displayNameOf(i); if (key.length()==0) return c.getSimpleName()+"."+e.getName(); try { ResourceBundleHolder rb = ResourceBundleHolder.get( c.getClassLoader().loadClass(c.getPackage().getName() + ".Messages")); return rb.format(key); } catch (ClassNotFoundException x) { LOGGER.log(WARNING, "Failed to load "+x.getMessage()+" for "+e.toString(),x); return key; } catch (MissingResourceException x) { LOGGER.log(WARNING, "Could not find key '" + key + "' in " + c.getPackage().getName() + ".Messages", x); return key; } } /** * Invokes the given initialization method. */ protected void invoke(Method e) { try { Class<?>[] pt = e.getParameterTypes(); Object[] args = new Object[pt.length]; for (int i=0; i<args.length; i++) args[i] = lookUp(pt[i]); e.invoke( Modifier.isStatic(e.getModifiers()) ? null : lookUp(e.getDeclaringClass()), args); } catch (IllegalAccessException x) { throw (Error)new IllegalAccessError().initCause(x); } catch (InvocationTargetException x) { throw new Error(x); } } /** * Determines the parameter injection of the initialization method. */ private Object lookUp(Class<?> type) { Jenkins j = Jenkins.getInstance(); assert j != null : "This method is only invoked after the Jenkins singleton instance has been set"; if (type==Jenkins.class || type==Hudson.class) return j; Injector i = j.getInjector(); if (i!=null) return i.getInstance(type); throw new IllegalArgumentException("Unable to inject "+type); } /** * Task implementation. */ public class TaskImpl implements Task { final Collection<Milestone> requires; final Collection<Milestone> attains; private final T i; private final Method e; private TaskImpl(T i, Method e) { this.i = i; this.e = e; requires = toMilestones(requiresOf(i), afterOf(i)); attains = toMilestones(attainsOf(i), beforeOf(i)); } /** * The annotation on the {@linkplain #getMethod() method} */ public T getAnnotation() { return i; } /** * Method that runs the initialization, that this task wraps. */ public Method getMethod() { return e; } public Collection<Milestone> requires() { return requires; } public Collection<Milestone> attains() { return attains; } public String getDisplayName() { return getDisplayNameOf(e, i); } public boolean failureIsFatal() { return fatalOf(i); } public void run(Reactor session) { invoke(e); } public String toString() { return e.toString(); } private Collection<Milestone> toMilestones(String[] tokens, Milestone m) { List<Milestone> r = new ArrayList<Milestone>(); for (String s : tokens) { try { r.add((Milestone)Enum.valueOf(milestoneType,s)); } catch (IllegalArgumentException x) { r.add(new MilestoneImpl(s)); } } r.add(m); return r; } } }