/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. */
  25. package hudson.triggers;
  26. import antlr.ANTLRException;
  27. import hudson.DependencyRunner;
  28. import hudson.DependencyRunner.ProjectRunnable;
  29. import hudson.DescriptorExtensionList;
  30. import hudson.Extension;
  31. import hudson.ExtensionPoint;
  32. import hudson.init.Initializer;
  33. import hudson.model.AbstractProject;
  34. import hudson.model.Action;
  35. import hudson.model.Build;
  36. import hudson.model.ComputerSet;
  37. import hudson.model.Describable;
  38. import hudson.model.Hudson;
  39. import hudson.model.Item;
  40. import hudson.model.PeriodicWork;
  41. import hudson.model.Project;
  42. import hudson.model.TopLevelItem;
  43. import hudson.model.TopLevelItemDescriptor;
  44. import hudson.scheduler.CronTab;
  45. import hudson.scheduler.CronTabList;
  46. import hudson.util.DoubleLaunchChecker;
  47. import java.io.InvalidObjectException;
  48. import java.io.ObjectStreamException;
  49. import java.util.ArrayList;
  50. import java.util.Calendar;
  51. import java.util.Collection;
  52. import java.util.Collections;
  53. import java.util.Date;
  54. import java.util.GregorianCalendar;
  55. import java.util.List;
  56. import java.util.Timer;
  57. import java.util.concurrent.Future;
  58. import java.util.logging.Level;
  59. import java.util.logging.Logger;
  60. import static hudson.init.InitMilestone.JOB_LOADED;
  61. /**
  62. * Triggers a {@link Build}.
  63. *
  64. * <p>
  65. * To register a custom {@link Trigger} from a plugin,
  66. * put {@link Extension} on your {@link TriggerDescriptor} class.
  67. *
  68. * @author Kohsuke Kawaguchi
  69. */
  70. public abstract class Trigger<J extends Item> implements Describable<Trigger<?>>, ExtensionPoint {
  71. /**
  72. * Called when a {@link Trigger} is loaded into memory and started.
  73. *
  74. * @param project
  75. * given so that the persisted form of this object won't have to have a back pointer.
  76. * @param newInstance
  77. * True if this is a newly created trigger first attached to the {@link Project}.
  78. * False if this is invoked for a {@link Project} loaded from disk.
  79. */
  80. public void start(J project, boolean newInstance) {
  81. this.job = project;
  82. }
  83. /**
  84. * Executes the triggered task.
  85. *
  86. * This method is invoked when {@link #Trigger(String)} is used
  87. * to create an instance, and the crontab matches the current time.
  88. */
  89. public void run() {}
  90. /**
  91. * Called before a {@link Trigger} is removed.
  92. * Under some circumstances, this may be invoked more than once for
  93. * a given {@link Trigger}, so be prepared for that.
  94. *
  95. * <p>
  96. * When the configuration is changed for a project, all triggers
  97. * are removed once and then added back.
  98. */
  99. public void stop() {}
  100. /**
  101. * Returns an action object if this {@link Trigger} has an action
  102. * to contribute to a {@link Project}.
  103. *
  104. * @deprecated as of 1.341
  105. * Use {@link #getProjectActions()} instead.
  106. */
  107. public Action getProjectAction() {
  108. return null;
  109. }
  110. /**
  111. * {@link Action}s to be displayed in the job page.
  112. *
  113. * @return
  114. * can be empty but never null
  115. * @since 1.341
  116. */
  117. public Collection<? extends Action> getProjectActions() {
  118. // delegate to getJobAction (singular) for backward compatible behavior
  119. Action a = getProjectAction();
  120. if (a==null) return Collections.emptyList();
  121. return Collections.singletonList(a);
  122. }
  123. public TriggerDescriptor getDescriptor() {
  124. return (TriggerDescriptor)Hudson.getInstance().getDescriptorOrDie(getClass());
  125. }
  126. protected final String spec;
  127. protected transient CronTabList tabs;
  128. protected transient J job;
  129. /**
  130. * Creates a new {@link Trigger} that gets {@link #run() run}
  131. * periodically. This is useful when your trigger does
  132. * some polling work.
  133. */
  134. protected Trigger(String cronTabSpec) throws ANTLRException {
  135. this.spec = cronTabSpec;
  136. this.tabs = CronTabList.create(cronTabSpec);
  137. }
  138. /**
  139. * Creates a new {@link Trigger} without using cron.
  140. */
  141. protected Trigger() {
  142. this.spec = "";
  143. this.tabs = new CronTabList(Collections.<CronTab>emptyList());
  144. }
  145. /**
  146. * Gets the crontab specification.
  147. *
  148. * If you are not using cron service, just ignore it.
  149. */
  150. public final String getSpec() {
  151. return spec;
  152. }
  153. protected Object readResolve() throws ObjectStreamException {
  154. try {
  155. tabs = CronTabList.create(spec);
  156. } catch (ANTLRException e) {
  157. InvalidObjectException x = new InvalidObjectException(e.getMessage());
  158. x.initCause(e);
  159. throw x;
  160. }
  161. return this;
  162. }
  163. /**
  164. * Runs every minute to check {@link TimerTrigger} and schedules build.
  165. */
  166. @Extension
  167. public static class Cron extends PeriodicWork {
  168. private final Calendar cal = new GregorianCalendar();
  169. public long getRecurrencePeriod() {
  170. return MIN;
  171. }
  172. public void doRun() {
  173. while(new Date().getTime()-cal.getTimeInMillis()>1000) {
  174. LOGGER.fine("cron checking "+cal.getTime().toLocaleString());
  175. try {
  176. checkTriggers(cal);
  177. } catch (Throwable e) {
  178. LOGGER.log(Level.WARNING,"Cron thread throw an exception",e);
  179. // bug in the code. Don't let the thread die.
  180. e.printStackTrace();
  181. }
  182. cal.add(Calendar.MINUTE,1);
  183. }
  184. }
  185. }
  186. private static Future previousSynchronousPolling;
  187. public static void checkTriggers(final Calendar cal) {
  188. Hudson inst = Hudson.getInstance();
  189. // Are we using synchronous polling?
  190. SCMTrigger.DescriptorImpl scmd = inst.getDescriptorByType(SCMTrigger.DescriptorImpl.class);
  191. if (scmd.synchronousPolling) {
  192. LOGGER.fine("using synchronous polling");
  193. // Check that previous synchronous polling job is done to prevent piling up too many jobs
  194. if (previousSynchronousPolling == null || previousSynchronousPolling.isDone()) {
  195. // Process SCMTriggers in the order of dependencies. Note that the crontab spec expressed per-project is
  196. // ignored, only the global setting is honored. The polling job is submitted only if the previous job has
  197. // terminated.
  198. // FIXME allow to set a global crontab spec
  199. previousSynchronousPolling = scmd.getExecutor().submit(new DependencyRunner(new ProjectRunnable() {
  200. public void run(AbstractProject p) {
  201. for (Trigger t : (Collection<Trigger>) p.getTriggers().values()) {
  202. if (t instanceof SCMTrigger) {
  203. LOGGER.fine("synchronously triggering SCMTrigger for project " + t.job.getName());
  204. t.run();
  205. }
  206. }
  207. }
  208. }));
  209. } else {
  210. LOGGER.fine("synchronous polling has detected unfinished jobs, will not trigger additional jobs.");
  211. }
  212. }
  213. // Process all triggers, except SCMTriggers when synchronousPolling is set
  214. for (AbstractProject<?,?> p : inst.getAllItems(AbstractProject.class)) {
  215. for (Trigger t : p.getTriggers().values()) {
  216. if (! (t instanceof SCMTrigger && scmd.synchronousPolling)) {
  217. LOGGER.fine("cron checking "+p.getName());
  218. if (t.tabs.check(cal)) {
  219. LOGGER.config("cron triggered "+p.getName());
  220. try {
  221. t.run();
  222. } catch (Throwable e) {
  223. // t.run() is a plugin, and some of them throw RuntimeException and other things.
  224. // don't let that cancel the polling activity. report and move on.
  225. LOGGER.log(Level.WARNING, t.getClass().getName()+".run() failed for "+p.getName(),e);
  226. }
  227. }
  228. }
  229. }
  230. }
  231. }
  232. private static final Logger LOGGER = Logger.getLogger(Trigger.class.getName());
  233. /**
  234. * This timer is available for all the components inside Hudson to schedule
  235. * some work.
  236. *
  237. * Initialized and cleaned up by {@link Hudson}, but value kept here for compatibility.
  238. *
  239. * If plugins want to run periodic jobs, they should implement {@link PeriodicWork}.
  240. */
  241. public static Timer timer;
  242. @Initializer(after=JOB_LOADED)
  243. public static void init() {
  244. new DoubleLaunchChecker().schedule();
  245. // start all PeridocWorks
  246. for(PeriodicWork p : PeriodicWork.all())
  247. timer.scheduleAtFixedRate(p,p.getInitialDelay(),p.getRecurrencePeriod());
  248. // start monitoring nodes, although there's no hurry.
  249. timer.schedule(new SafeTimerTask() {
  250. public void doRun() {
  251. ComputerSet.initialize();
  252. }
  253. }, 1000*10);
  254. }
  255. /**
  256. * Returns all the registered {@link Trigger} descriptors.
  257. */
  258. public static DescriptorExtensionList<Trigger<?>,TriggerDescriptor> all() {
  259. return (DescriptorExtensionList)Hudson.getInstance().getDescriptorList(Trigger.class);
  260. }
  261. /**
  262. * Returns a subset of {@link TriggerDescriptor}s that applys to the given item.
  263. */
  264. public static List<TriggerDescriptor> for_(Item i) {
  265. List<TriggerDescriptor> r = new ArrayList<TriggerDescriptor>();
  266. for (TriggerDescriptor t : all()) {
  267. if(!t.isApplicable(i)) continue;
  268. if (i instanceof TopLevelItem) {// ugly
  269. TopLevelItemDescriptor tld = ((TopLevelItem) i).getDescriptor();
  270. // tld shouldn't be really null in contract, but we often write test Describables that
  271. // doesn't have a Descriptor.
  272. if(tld!=null && !tld.isApplicable(t)) continue;
  273. }
  274. r.add(t);
  275. }
  276. return r;
  277. }
  278. @Override
  279. public boolean equals(Object o) {
  280. if (this == o) {
  281. return true;
  282. }
  283. if (o == null || getClass() != o.getClass()) {
  284. return false;
  285. }
  286. Trigger trigger = (Trigger) o;
  287. if (spec != null ? !spec.equals(trigger.spec) : trigger.spec != null) {
  288. return false;
  289. }
  290. return true;
  291. }
  292. @Override
  293. public int hashCode() {
  294. return spec != null ? spec.hashCode() : 0;
  295. }
  296. }