PageRenderTime 41ms CodeModel.GetById 2ms app.highlight 33ms RepoModel.GetById 2ms app.codeStats 0ms

/hudson-core/src/main/java/hudson/triggers/Trigger.java

http://github.com/hudson/hudson
Java | 338 lines | 180 code | 42 blank | 116 comment | 29 complexity | 55d435ffe2129c18e48086ff991e6823 MD5 | raw file
  1/*
  2 * The MIT License
  3 * 
  4 * Copyright (c) 2004-2011, Oracle Corporation, Kohsuke Kawaguchi, Brian Westrich, Jean-Baptiste Quenot,
  5 * Stephen Connolly, Tom Huybrechts, Nikita Levyankov
  6 * 
  7 * Permission is hereby granted, free of charge, to any person obtaining a copy
  8 * of this software and associated documentation files (the "Software"), to deal
  9 * in the Software without restriction, including without limitation the rights
 10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11 * copies of the Software, and to permit persons to whom the Software is
 12 * furnished to do so, subject to the following conditions:
 13 * 
 14 * The above copyright notice and this permission notice shall be included in
 15 * all copies or substantial portions of the Software.
 16 * 
 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23 * THE SOFTWARE.
 24 */
 25package hudson.triggers;
 26
 27import antlr.ANTLRException;
 28import hudson.DependencyRunner;
 29import hudson.DependencyRunner.ProjectRunnable;
 30import hudson.DescriptorExtensionList;
 31import hudson.Extension;
 32import hudson.ExtensionPoint;
 33import hudson.init.Initializer;
 34import hudson.model.AbstractProject;
 35import hudson.model.Action;
 36import hudson.model.Build;
 37import hudson.model.ComputerSet;
 38import hudson.model.Describable;
 39import hudson.model.Hudson;
 40import hudson.model.Item;
 41import hudson.model.PeriodicWork;
 42import hudson.model.Project;
 43import hudson.model.TopLevelItem;
 44import hudson.model.TopLevelItemDescriptor;
 45import hudson.scheduler.CronTab;
 46import hudson.scheduler.CronTabList;
 47import hudson.util.DoubleLaunchChecker;
 48import java.io.InvalidObjectException;
 49import java.io.ObjectStreamException;
 50import java.util.ArrayList;
 51import java.util.Calendar;
 52import java.util.Collection;
 53import java.util.Collections;
 54import java.util.Date;
 55import java.util.GregorianCalendar;
 56import java.util.List;
 57import java.util.Timer;
 58import java.util.concurrent.Future;
 59import java.util.logging.Level;
 60import java.util.logging.Logger;
 61
 62import static hudson.init.InitMilestone.JOB_LOADED;
 63
 64/**
 65 * Triggers a {@link Build}.
 66 *
 67 * <p>
 68 * To register a custom {@link Trigger} from a plugin,
 69 * put {@link Extension} on your {@link TriggerDescriptor} class.
 70 *
 71 * @author Kohsuke Kawaguchi
 72 */
 73public abstract class Trigger<J extends Item> implements Describable<Trigger<?>>, ExtensionPoint {
 74
 75    /**
 76     * Called when a {@link Trigger} is loaded into memory and started.
 77     *
 78     * @param project
 79     *      given so that the persisted form of this object won't have to have a back pointer.
 80     * @param newInstance
 81     *      True if this is a newly created trigger first attached to the {@link Project}.
 82     *      False if this is invoked for a {@link Project} loaded from disk.
 83     */
 84    public void start(J project, boolean newInstance) {
 85        this.job = project;
 86    }
 87
 88    /**
 89     * Executes the triggered task.
 90     *
 91     * This method is invoked when {@link #Trigger(String)} is used
 92     * to create an instance, and the crontab matches the current time.
 93     */
 94    public void run() {}
 95
 96    /**
 97     * Called before a {@link Trigger} is removed.
 98     * Under some circumstances, this may be invoked more than once for
 99     * a given {@link Trigger}, so be prepared for that.
100     *
101     * <p>
102     * When the configuration is changed for a project, all triggers
103     * are removed once and then added back.
104     */
105    public void stop() {}
106
107    /**
108     * Returns an action object if this {@link Trigger} has an action
109     * to contribute to a {@link Project}.
110     *
111     * @deprecated as of 1.341
112     *      Use {@link #getProjectActions()} instead.
113     */
114    public Action getProjectAction() {
115        return null;
116    }
117
118    /**
119     * {@link Action}s to be displayed in the job page.
120     *
121     * @return
122     *      can be empty but never null
123     * @since 1.341
124     */
125    public Collection<? extends Action> getProjectActions() {
126        // delegate to getJobAction (singular) for backward compatible behavior
127        Action a = getProjectAction();
128        if (a==null)    return Collections.emptyList();
129        return Collections.singletonList(a);
130    }
131
132    public TriggerDescriptor getDescriptor() {
133        return (TriggerDescriptor)Hudson.getInstance().getDescriptorOrDie(getClass());
134    }
135
136
137
138    protected final String spec;
139    protected transient CronTabList tabs;
140    protected transient J job;
141
142    /**
143     * Creates a new {@link Trigger} that gets {@link #run() run}
144     * periodically. This is useful when your trigger does
145     * some polling work.
146     */
147    protected Trigger(String cronTabSpec) throws ANTLRException {
148        this.spec = cronTabSpec;
149        this.tabs = CronTabList.create(cronTabSpec);
150    }
151
152    /**
153     * Creates a new {@link Trigger} without using cron.
154     */
155    protected Trigger() {
156        this.spec = "";
157        this.tabs = new CronTabList(Collections.<CronTab>emptyList());
158    }
159
160    /**
161     * Gets the crontab specification.
162     *
163     * If you are not using cron service, just ignore it.
164     */
165    public final String getSpec() {
166        return spec;
167    }
168
169    protected Object readResolve() throws ObjectStreamException {
170        try {
171            tabs = CronTabList.create(spec);
172        } catch (ANTLRException e) {
173            InvalidObjectException x = new InvalidObjectException(e.getMessage());
174            x.initCause(e);
175            throw x;
176        }
177        return this;
178    }
179
180
181    /**
182     * Runs every minute to check {@link TimerTrigger} and schedules build.
183     */
184    @Extension
185    public static class Cron extends PeriodicWork {
186        private final Calendar cal = new GregorianCalendar();
187
188        public long getRecurrencePeriod() {
189            return MIN;
190        }
191
192        public void doRun() {
193            while(new Date().getTime()-cal.getTimeInMillis()>1000) {
194                LOGGER.fine("cron checking "+cal.getTime().toLocaleString());
195
196                try {
197                    checkTriggers(cal);
198                } catch (Throwable e) {
199                    LOGGER.log(Level.WARNING,"Cron thread throw an exception",e);
200                    // bug in the code. Don't let the thread die.
201                    e.printStackTrace();
202                }
203
204                cal.add(Calendar.MINUTE,1);
205            }
206        }
207    }
208
209    private static Future previousSynchronousPolling;
210
211    public static void checkTriggers(final Calendar cal) {
212        Hudson inst = Hudson.getInstance();
213
214        // Are we using synchronous polling?
215        SCMTrigger.DescriptorImpl scmd = inst.getDescriptorByType(SCMTrigger.DescriptorImpl.class);
216        if (scmd.synchronousPolling) {
217            LOGGER.fine("using synchronous polling");
218
219            // Check that previous synchronous polling job is done to prevent piling up too many jobs
220            if (previousSynchronousPolling == null || previousSynchronousPolling.isDone()) {
221                // Process SCMTriggers in the order of dependencies. Note that the crontab spec expressed per-project is
222                // ignored, only the global setting is honored. The polling job is submitted only if the previous job has
223                // terminated.
224                // FIXME allow to set a global crontab spec
225                previousSynchronousPolling = scmd.getExecutor().submit(new DependencyRunner(new ProjectRunnable() {
226                    public void run(AbstractProject p) {
227                        for (Trigger t : (Collection<Trigger>) p.getTriggers().values()) {
228                            if (t instanceof SCMTrigger) {
229                                LOGGER.fine("synchronously triggering SCMTrigger for project " + t.job.getName());
230                                t.run();
231                            }
232                        }
233                    }
234                }));
235            } else {
236                LOGGER.fine("synchronous polling has detected unfinished jobs, will not trigger additional jobs.");
237            }
238        }
239
240        // Process all triggers, except SCMTriggers when synchronousPolling is set
241        for (AbstractProject<?,?> p : inst.getAllItems(AbstractProject.class)) {
242            for (Trigger t : p.getTriggers().values()) {
243                if (! (t instanceof SCMTrigger && scmd.synchronousPolling)) {
244                    LOGGER.fine("cron checking "+p.getName());
245
246                    if (t.tabs.check(cal)) {
247                        LOGGER.config("cron triggered "+p.getName());
248                        try {
249                            t.run();
250                        } catch (Throwable e) {
251                            // t.run() is a plugin, and some of them throw RuntimeException and other things.
252                            // don't let that cancel the polling activity. report and move on.
253                            LOGGER.log(Level.WARNING, t.getClass().getName()+".run() failed for "+p.getName(),e);
254                        }
255                    }
256                }
257            }
258        }
259    }
260
261    private static final Logger LOGGER = Logger.getLogger(Trigger.class.getName());
262
263    /**
264     * This timer is available for all the components inside Hudson to schedule
265     * some work.
266     *
267     * Initialized and cleaned up by {@link Hudson}, but value kept here for compatibility.
268     *
269     * If plugins want to run periodic jobs, they should implement {@link PeriodicWork}.
270     */
271    public static Timer timer;
272
273    @Initializer(after=JOB_LOADED)
274    public static void init() {
275        new DoubleLaunchChecker().schedule();
276
277        // start all PeridocWorks
278        for(PeriodicWork p : PeriodicWork.all())
279            timer.scheduleAtFixedRate(p,p.getInitialDelay(),p.getRecurrencePeriod());
280
281        // start monitoring nodes, although there's no hurry.
282        timer.schedule(new SafeTimerTask() {
283            public void doRun() {
284                ComputerSet.initialize();
285            }
286        }, 1000*10);
287    }
288
289    /**
290     * Returns all the registered {@link Trigger} descriptors.
291     */
292    public static DescriptorExtensionList<Trigger<?>,TriggerDescriptor> all() {
293        return (DescriptorExtensionList)Hudson.getInstance().getDescriptorList(Trigger.class);
294    }
295
296    /**
297     * Returns a subset of {@link TriggerDescriptor}s that applys to the given item.
298     */
299    public static List<TriggerDescriptor> for_(Item i) {
300        List<TriggerDescriptor> r = new ArrayList<TriggerDescriptor>();
301        for (TriggerDescriptor t : all()) {
302            if(!t.isApplicable(i))  continue;
303
304            if (i instanceof TopLevelItem) {// ugly
305                TopLevelItemDescriptor tld = ((TopLevelItem) i).getDescriptor();
306                // tld shouldn't be really null in contract, but we often write test Describables that
307                // doesn't have a Descriptor.
308                if(tld!=null && !tld.isApplicable(t))    continue;
309            }
310
311            r.add(t);
312        }
313        return r;
314    }
315
316    @Override
317    public boolean equals(Object o) {
318        if (this == o) {
319            return true;
320        }
321        if (o == null || getClass() != o.getClass()) {
322            return false;
323        }
324
325        Trigger trigger = (Trigger) o;
326
327        if (spec != null ? !spec.equals(trigger.spec) : trigger.spec != null) {
328            return false;
329        }
330
331        return true;
332    }
333
334    @Override
335    public int hashCode() {
336        return spec != null ? spec.hashCode() : 0;
337    }
338}