File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\triggers\SCMTrigger.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Brian Westrich, Jean-Baptiste Quenot, id:cactusman
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.triggers;
25
26 import antlr.ANTLRException;
27 import hudson.Util;
28 import hudson.Extension;
29 import hudson.console.AnnotatedLargeText;
30 import hudson.model.AbstractBuild;
31 import hudson.model.AbstractProject;
32 import hudson.model.Action;
33 import hudson.model.Cause;
34 import jenkins.model.Jenkins;
35 import hudson.model.Item;
36 import hudson.model.Project;
37 import hudson.model.SCMedItem;
38 import hudson.model.AdministrativeMonitor;
39 import hudson.util.FlushProofOutputStream;
40 import hudson.util.FormValidation;
41 import hudson.util.StreamTaskListener;
42 import hudson.util.TimeUnit2;
43 import hudson.util.SequentialExecutionQueue;
44 import org.apache.commons.io.FileUtils;
45 import org.apache.commons.jelly.XMLOutput;
46 import org.kohsuke.stapler.StaplerRequest;
47 import org.kohsuke.stapler.DataBoundConstructor;
48
49 import java.io.File;
50 import java.io.IOException;
51 import java.io.PrintStream;
52 import java.nio.charset.Charset;
53 import java.util.Collection;
54 import java.util.Collections;
55 import java.util.Date;
56 import java.util.List;
57 import java.util.ArrayList;
58 import java.util.concurrent.ExecutorService;
59 import java.util.concurrent.Executors;
60 import java.util.logging.Level;
61 import java.util.logging.Logger;
62 import java.text.DateFormat;
63
64 import net.sf.json.JSONObject;
65 import org.kohsuke.stapler.QueryParameter;
66 import org.kohsuke.stapler.StaplerResponse;
67
68 import static java.util.logging.Level.*;
69
70 /**
71 * {@link Trigger} that checks for SCM updates periodically.
72 *
73 * @author Kohsuke Kawaguchi
74 */
75 public class SCMTrigger extends Trigger<SCMedItem> {
76
77 private boolean ignorePostCommitHooks;
78
79 public SCMTrigger(String scmpoll_spec) throws ANTLRException {
80 this(scmpoll_spec, false);
81 }
82
83 @DataBoundConstructor
84 public SCMTrigger(String scmpoll_spec, boolean ignorePostCommitHooks) throws ANTLRException {
85 super(scmpoll_spec);
86 this.ignorePostCommitHooks = ignorePostCommitHooks;
87 }
88
89 /**
90 * This trigger wants to ignore post-commit hooks.
91 * <p>
92 * SCM plugins must respect this and not run this trigger for post-commit notifications.
93 *
94 * @since 1.493
95 */
96 public boolean isIgnorePostCommitHooks() {
97 return this.ignorePostCommitHooks;
98 }
99
100 @Override
101 public void run() {
102 run(null);
103 }
104
105 /**
106 * Run the SCM trigger with additional build actions. Used by SubversionRepositoryStatus
107 * to trigger a build at a specific revisionn number.
108 *
109 * @param additionalActions
110 * @since 1.375
111 */
112 public void run(Action[] additionalActions) {
113 if(Jenkins.getInstance().isQuietingDown())
114 return; // noop
115
116 DescriptorImpl d = getDescriptor();
117
118 LOGGER.fine("Scheduling a polling for "+job);
119 if (d.synchronousPolling) {
120 LOGGER.fine("Running the trigger directly without threading, " +
121 "as it's already taken care of by Trigger.Cron");
122 new Runner(additionalActions).run();
123 } else {
124 // schedule the polling.
125 // even if we end up submitting this too many times, that's OK.
126 // the real exclusion control happens inside Runner.
127 LOGGER.fine("scheduling the trigger to (asynchronously) run");
128 d.queue.execute(new Runner(additionalActions));
129 d.clogCheck();
130 }
131 }
132
133 @Override
134 public DescriptorImpl getDescriptor() {
135 return (DescriptorImpl)super.getDescriptor();
136 }
137
138 @Override
139 public Collection<? extends Action> getProjectActions() {
140 return Collections.singleton(new SCMAction());
141 }
142
143 /**
144 * Returns the file that records the last/current polling activity.
145 */
146 public File getLogFile() {
147 return new File(job.getRootDir(),"scm-polling.log");
148 }
149
150 @Extension
151 public static class DescriptorImpl extends TriggerDescriptor {
152 /**
153 * Used to control the execution of the polling tasks.
154 * <p>
155 * This executor implementation has a semantics suitable for polling. Namely, no two threads will try to poll the same project
156 * at once, and multiple polling requests to the same job will be combined into one. Note that because executor isn't aware
157 * of a potential workspace lock between a build and a polling, we may end up using executor threads unwisely --- they
158 * may block.
159 */
160 private transient final SequentialExecutionQueue queue = new SequentialExecutionQueue(Executors.newSingleThreadExecutor());
161
162 /**
163 * Whether the projects should be polled all in one go in the order of dependencies. The default behavior is
164 * that each project polls for changes independently.
165 */
166 public boolean synchronousPolling = false;
167
168 /**
169 * Max number of threads for SCM polling.
170 * 0 for unbounded.
171 */
172 private int maximumThreads;
173
174 public DescriptorImpl() {
175 load();
176 resizeThreadPool();
177 }
178
179 public boolean isApplicable(Item item) {
180 return item instanceof SCMedItem;
181 }
182
183 public ExecutorService getExecutor() {
184 return queue.getExecutors();
185 }
186
187 /**
188 * Returns true if the SCM polling thread queue has too many jobs
189 * than it can handle.
190 */
191 public boolean isClogged() {
192 return queue.isStarving(STARVATION_THRESHOLD);
193 }
194
195 /**
196 * Checks if the queue is clogged, and if so,
197 * activate {@link AdministrativeMonitorImpl}.
198 */
199 public void clogCheck() {
200 AdministrativeMonitor.all().get(AdministrativeMonitorImpl.class).on = isClogged();
201 }
202
203 /**
204 * Gets the snapshot of {@link Runner}s that are performing polling.
205 */
206 public List<Runner> getRunners() {
207 return Util.filter(queue.getInProgress(),Runner.class);
208 }
209
210 /**
211 * Gets the snapshot of {@link SCMedItem}s that are being polled at this very moment.
212 */
213 public List<SCMedItem> getItemsBeingPolled() {
214 List<SCMedItem> r = new ArrayList<SCMedItem>();
215 for (Runner i : getRunners())
216 r.add(i.getTarget());
217 return r;
218 }
219
220 public String getDisplayName() {
221 return Messages.SCMTrigger_DisplayName();
222 }
223
224 /**
225 * Gets the number of concurrent threads used for polling.
226 *
227 * @return
228 * 0 if unlimited.
229 */
230 public int getPollingThreadCount() {
231 return maximumThreads;
232 }
233
234 /**
235 * Sets the number of concurrent threads used for SCM polling and resizes the thread pool accordingly
236 * @param n number of concurrent threads, zero or less means unlimited, maximum is 100
237 */
238 public void setPollingThreadCount(int n) {
239 // fool proof
240 if(n<0) n=0;
241 if(n>100) n=100;
242
243 maximumThreads = n;
244
245 resizeThreadPool();
246 }
247
248 /**
249 * Update the {@link ExecutorService} instance.
250 */
251 /*package*/ synchronized void resizeThreadPool() {
252 queue.setExecutors(
253 (maximumThreads==0 ? Executors.newCachedThreadPool() : Executors.newFixedThreadPool(maximumThreads)));
254 }
255
256 @Override
257 public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
258 String t = json.optString("pollingThreadCount",null);
259 if(t==null || t.length()==0)
260 setPollingThreadCount(0);
261 else
262 setPollingThreadCount(Integer.parseInt(t));
263
264 // Save configuration
265 save();
266
267 return true;
268 }
269
270 public FormValidation doCheckPollingThreadCount(@QueryParameter String value) {
271 if (value != null && "".equals(value.trim()))
272 return FormValidation.ok();
273 return FormValidation.validateNonNegativeInteger(value);
274 }
275 }
276
277 @Extension
278 public static final class AdministrativeMonitorImpl extends AdministrativeMonitor {
279 private boolean on;
280
281 public boolean isActivated() {
282 return on;
283 }
284 }
285
286 /**
287 * Associated with {@link AbstractBuild} to show the polling log
288 * that triggered that build.
289 *
290 * @since 1.376
291 */
292 public static class BuildAction implements Action {
293 public final AbstractBuild build;
294
295 public BuildAction(AbstractBuild build) {
296 this.build = build;
297 }
298
299 /**
300 * Polling log that triggered the build.
301 */
302 public File getPollingLogFile() {
303 return new File(build.getRootDir(),"polling.log");
304 }
305
306 public String getIconFileName() {
307 return "clipboard.png";
308 }
309
310 public String getDisplayName() {
311 return Messages.SCMTrigger_BuildAction_DisplayName();
312 }
313
314 public String getUrlName() {
315 return "pollingLog";
316 }
317
318 /**
319 * Sends out the raw polling log output.
320 */
321 public void doPollingLog(StaplerRequest req, StaplerResponse rsp) throws IOException {
322 rsp.setContentType("text/plain;charset=UTF-8");
323 // Prevent jelly from flushing stream so Content-Length header can be added afterwards
324 FlushProofOutputStream out = new FlushProofOutputStream(rsp.getCompressedOutputStream(req));
325 getPollingLogText().writeLogTo(0, out);
326 out.close();
327 }
328
329 public AnnotatedLargeText getPollingLogText() {
330 return new AnnotatedLargeText<BuildAction>(getPollingLogFile(), Charset.defaultCharset(), true, this);
331 }
332
333 /**
334 * Used from <tt>polling.jelly</tt> to write annotated polling log to the given output.
335 */
336 public void writePollingLogTo(long offset, XMLOutput out) throws IOException {
337 // TODO: resurrect compressed log file support
338 getPollingLogText().writeHtmlTo(offset, out.asWriter());
339 }
340 }
341
342 /**
343 * Action object for {@link Project}. Used to display the last polling log.
344 */
345 public final class SCMAction implements Action {
346 public AbstractProject<?,?> getOwner() {
347 return job.asProject();
348 }
349
350 public String getIconFileName() {
351 return "clipboard.png";
352 }
353
354 public String getDisplayName() {
355 return Messages.SCMTrigger_getDisplayName(job.getScm().getDescriptor().getDisplayName());
356 }
357
358 public String getUrlName() {
359 return "scmPollLog";
360 }
361
362 public String getLog() throws IOException {
363 return Util.loadFile(getLogFile());
364 }
365
366 /**
367 * Writes the annotated log to the given output.
368 * @since 1.350
369 */
370 public void writeLogTo(XMLOutput out) throws IOException {
371 new AnnotatedLargeText<SCMAction>(getLogFile(),Charset.defaultCharset(),true,this).writeHtmlTo(0,out.asWriter());
372 }
373 }
374
375 private static final Logger LOGGER = Logger.getLogger(SCMTrigger.class.getName());
376
377 /**
378 * {@link Runnable} that actually performs polling.
379 */
380 public class Runner implements Runnable {
381
382 /**
383 * When did the polling start?
384 */
385 private volatile long startTime;
386
387 private Action[] additionalActions;
388
389 public Runner() {
390 additionalActions = new Action[0];
391 }
392
393 public Runner(Action[] actions) {
394 if (actions == null) {
395 additionalActions = new Action[0];
396 } else {
397 additionalActions = actions;
398 }
399 }
400
401 /**
402 * Where the log file is written.
403 */
404 public File getLogFile() {
405 return SCMTrigger.this.getLogFile();
406 }
407
408 /**
409 * For which {@link Item} are we polling?
410 */
411 public SCMedItem getTarget() {
412 return job;
413 }
414
415 /**
416 * When was this polling started?
417 */
418 public long getStartTime() {
419 return startTime;
420 }
421
422 /**
423 * Human readable string of when this polling is started.
424 */
425 public String getDuration() {
426 return Util.getTimeSpanString(System.currentTimeMillis()-startTime);
427 }
428
429 private boolean runPolling() {
430 try {
431 // to make sure that the log file contains up-to-date text,
432 // don't do buffering.
433 StreamTaskListener listener = new StreamTaskListener(getLogFile());
434
435 try {
436 PrintStream logger = listener.getLogger();
437 long start = System.currentTimeMillis();
438 logger.println("Started on "+ DateFormat.getDateTimeInstance().format(new Date()));
439 boolean result = job.poll(listener).hasChanges();
440 logger.println("Done. Took "+ Util.getTimeSpanString(System.currentTimeMillis()-start));
441 if(result)
442 logger.println("Changes found");
443 else
444 logger.println("No changes");
445 return result;
446 } catch (Error e) {
447 e.printStackTrace(listener.error("Failed to record SCM polling for "+job));
448 LOGGER.log(Level.SEVERE,"Failed to record SCM polling for "+job,e);
449 throw e;
450 } catch (RuntimeException e) {
451 e.printStackTrace(listener.error("Failed to record SCM polling for "+job));
452 LOGGER.log(Level.SEVERE,"Failed to record SCM polling for "+job,e);
453 throw e;
454 } finally {
455 listener.close();
456 }
457 } catch (IOException e) {
458 LOGGER.log(Level.SEVERE,"Failed to record SCM polling for "+job,e);
459 return false;
460 }
461 }
462
463 public void run() {
464 String threadName = Thread.currentThread().getName();
465 Thread.currentThread().setName("SCM polling for "+job);
466 try {
467 startTime = System.currentTimeMillis();
468 if(runPolling()) {
469 AbstractProject p = job.asProject();
470 String name = " #"+p.getNextBuildNumber();
471 SCMTriggerCause cause;
472 try {
473 cause = new SCMTriggerCause(getLogFile());
474 } catch (IOException e) {
475 LOGGER.log(WARNING, "Failed to parse the polling log",e);
476 cause = new SCMTriggerCause();
477 }
478 if(p.scheduleBuild(p.getQuietPeriod(), cause, additionalActions)) {
479 LOGGER.info("SCM changes detected in "+ job.getName()+". Triggering "+name);
480 } else {
481 LOGGER.info("SCM changes detected in "+ job.getName()+". Job is already in the queue");
482 }
483 }
484 } finally {
485 Thread.currentThread().setName(threadName);
486 }
487 }
488
489 private SCMedItem job() {
490 return job;
491 }
492
493 // as per the requirement of SequentialExecutionQueue, value equality is necessary
494 @Override
495 public boolean equals(Object that) {
496 return that instanceof Runner && job()==((Runner)that).job();
497 }
498
499 @Override
500 public int hashCode() {
501 return job.hashCode();
502 }
503 }
504
505 public static class SCMTriggerCause extends Cause {
506 /**
507 * Only used while ths cause is in the queue.
508 * Once attached to the build, we'll move this into a file to reduce the memory footprint.
509 */
510 private String pollingLog;
511
512 public SCMTriggerCause(File logFile) throws IOException {
513 // TODO: charset of this log file?
514 this(FileUtils.readFileToString(logFile));
515 }
516
517 public SCMTriggerCause(String pollingLog) {
518 this.pollingLog = pollingLog;
519 }
520
521 /**
522 * @deprecated
523 * Use {@link #SCMTriggerCause(String)}.
524 */
525 public SCMTriggerCause() {
526 this("");
527 }
528
529 @Override
530 public void onAddedTo(AbstractBuild build) {
531 BuildAction oldAction = build.getAction(BuildAction.class);
532 if (oldAction != null) {
533 build.getActions().remove(oldAction);
534 }
535
536 try {
537 BuildAction a = new BuildAction(build);
538 FileUtils.writeStringToFile(a.getPollingLogFile(),pollingLog);
539 build.addAction(a);
540 } catch (IOException e) {
541 LOGGER.log(WARNING,"Failed to persist the polling log",e);
542 }
543 pollingLog = null;
544 }
545
546 @Override
547 public String getShortDescription() {
548 return Messages.SCMTrigger_SCMTriggerCause_ShortDescription();
549 }
550
551 @Override
552 public boolean equals(Object o) {
553 return o instanceof SCMTriggerCause;
554 }
555
556 @Override
557 public int hashCode() {
558 return 3;
559 }
560 }
561
562 /**
563 * How long is too long for a polling activity to be in the queue?
564 */
565 public static long STARVATION_THRESHOLD =Long.getLong(SCMTrigger.class.getName()+".starvationThreshold", TimeUnit2.HOURS.toMillis(1));
566 }
567