KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > hudson > model > Job


1 package hudson.model;
2
3 import hudson.ExtensionPoint;
4 import hudson.Util;
5 import hudson.tasks.BuildTrigger;
6 import hudson.tasks.LogRotator;
7 import hudson.util.ChartUtil;
8 import hudson.util.ColorPalette;
9 import hudson.util.CopyOnWriteList;
10 import hudson.util.DataSetBuilder;
11 import hudson.util.IOException2;
12 import hudson.util.RunList;
13 import hudson.util.ShiftedCategoryAxis;
14 import hudson.util.StackedAreaRenderer2;
15 import hudson.util.TextFile;
16 import org.apache.tools.ant.taskdefs.Copy;
17 import org.apache.tools.ant.types.FileSet;
18 import org.jfree.chart.ChartFactory;
19 import org.jfree.chart.JFreeChart;
20 import org.jfree.chart.axis.CategoryAxis;
21 import org.jfree.chart.axis.CategoryLabelPositions;
22 import org.jfree.chart.axis.NumberAxis;
23 import org.jfree.chart.plot.CategoryPlot;
24 import org.jfree.chart.plot.PlotOrientation;
25 import org.jfree.chart.renderer.category.StackedAreaRenderer;
26 import org.jfree.data.category.CategoryDataset;
27 import org.jfree.ui.RectangleInsets;
28 import org.kohsuke.stapler.StaplerRequest;
29 import org.kohsuke.stapler.StaplerResponse;
30
31 import javax.servlet.ServletException JavaDoc;
32 import javax.servlet.http.HttpServletResponse JavaDoc;
33 import java.awt.Color JavaDoc;
34 import java.awt.Paint JavaDoc;
35 import java.io.File JavaDoc;
36 import java.io.IOException JavaDoc;
37 import java.util.ArrayList JavaDoc;
38 import java.util.Collection JavaDoc;
39 import java.util.Collections JavaDoc;
40 import java.util.List JavaDoc;
41 import java.util.Map JavaDoc;
42 import java.util.SortedMap JavaDoc;
43
44 /**
45  * A job is an runnable entity under the monitoring of Hudson.
46  *
47  * <p>
48  * Every time it "runs", it will be recorded as a {@link Run} object.
49  *
50  * @author Kohsuke Kawaguchi
51  */

52 public abstract class Job<JobT extends Job<JobT,RunT>, RunT extends Run<JobT,RunT>>
53         extends AbstractItem implements ExtensionPoint {
54
55     /**
56      * Next build number.
57      * Kept in a separate file because this is the only information
58      * that gets updated often. This allows the rest of the configuration
59      * to be in the VCS.
60      * <p>
61      * In 1.28 and earlier, this field was stored in the project configuration file,
62      * so even though this is marked as transient, don't move it around.
63      */

64     protected transient int nextBuildNumber = 1;
65
66     private LogRotator logRotator;
67
68     private boolean keepDependencies;
69
70     /**
71      * List of {@link UserProperty}s configured for this project.
72      */

73     private CopyOnWriteList<JobProperty<? super JobT>> properties = new CopyOnWriteList<JobProperty<? super JobT>>();
74
75     protected Job(ItemGroup parent,String JavaDoc name) {
76         super(parent,name);
77         getBuildDir().mkdirs();
78     }
79
80     public void onLoad(ItemGroup<? extends Item> parent, String JavaDoc name) throws IOException JavaDoc {
81         super.onLoad(parent, name);
82
83         TextFile f = getNextBuildNumberFile();
84         if(f.exists()) {
85             // starting 1.28, we store nextBuildNumber in a separate file.
86
// but old Hudson didn't do it, so if the file doesn't exist,
87
// assume that nextBuildNumber was read from config.xml
88
try {
89                 this.nextBuildNumber = Integer.parseInt(f.readTrim());
90             } catch (NumberFormatException JavaDoc e) {
91                 throw new IOException2(f+" doesn't contain a number",e);
92             }
93         } else {
94             // this must be the old Hudson. create this file now.
95
saveNextBuildNumber();
96             save(); // and delete it from the config.xml
97
}
98
99         if(properties==null) // didn't exist < 1.72
100
properties = new CopyOnWriteList<JobProperty<? super JobT>>();
101     }
102
103     @Override JavaDoc
104     public void onCopiedFrom(Item src) {
105         super.onCopiedFrom(src);
106         this.nextBuildNumber = 1; // reset the next build number
107
}
108
109     private TextFile getNextBuildNumberFile() {
110         return new TextFile(new File(this.getRootDir(),"nextBuildNumber"));
111     }
112
113     protected void saveNextBuildNumber() throws IOException JavaDoc {
114         getNextBuildNumberFile().write(String.valueOf(nextBuildNumber)+'\n');
115     }
116
117     public boolean isInQueue() {
118         return false;
119     }
120
121     /**
122      * If true, it will keep all the build logs of dependency components.
123      */

124     public boolean isKeepDependencies() {
125         return keepDependencies;
126     }
127
128     /**
129      * Allocates a new buildCommand number.
130      */

131     public synchronized int assignBuildNumber() throws IOException JavaDoc {
132         int r = nextBuildNumber++;
133         saveNextBuildNumber();
134         return r;
135     }
136
137     /**
138      * Peeks the next build number.
139      */

140     public int getNextBuildNumber() {
141         return nextBuildNumber;
142     }
143
144     /**
145      * Returns the log rotator for this job, or null if none.
146      */

147     public LogRotator getLogRotator() {
148         return logRotator;
149     }
150
151     public void setLogRotator(LogRotator logRotator) {
152         this.logRotator = logRotator;
153     }
154
155     public Collection JavaDoc<? extends Job> getAllJobs() {
156         return Collections.<Job>singleton(this);
157     }
158
159     /**
160      * Gets all the job properties configured for this job.
161      */

162     @SuppressWarnings JavaDoc("unchecked")
163     public Map JavaDoc<JobPropertyDescriptor,JobProperty<? super JobT>> getProperties() {
164         return Descriptor.toMap((Iterable JavaDoc)properties);
165     }
166
167     /**
168      * Gets the specific property, or null if the propert is not configured for this job.
169      */

170     public <T extends JobProperty> T getProperty(Class JavaDoc<T> clazz) {
171         for (JobProperty p : properties) {
172             if(clazz.isInstance(p))
173                 return clazz.cast(p);
174         }
175         return null;
176     }
177
178     /**
179      * Renames a job.
180      *
181      * <p>
182      * This method is defined on {@link Job} but really only applicable
183      * for {@link Job}s that are top-level items.
184      */

185     public void renameTo(String JavaDoc newName) throws IOException JavaDoc {
186         // always synchronize from bigger objects first
187
final Hudson parent = Hudson.getInstance();
188         assert this instanceof TopLevelItem;
189         synchronized(parent) {
190             synchronized(this) {
191                 // sanity check
192
if(newName==null)
193                     throw new IllegalArgumentException JavaDoc("New name is not given");
194                 if(parent.getItem(newName)!=null)
195                     throw new IllegalArgumentException JavaDoc("Job "+newName+" already exists");
196
197                 // noop?
198
if(this.name.equals(newName))
199                     return;
200
201
202                 String JavaDoc oldName = this.name;
203                 File oldRoot = this.getRootDir();
204
205                 doSetName(newName);
206                 File newRoot = this.getRootDir();
207
208                 {// rename data files
209
boolean interrupted=false;
210                     boolean renamed = false;
211
212                     // try to rename the job directory.
213
// this may fail on Windows due to some other processes accessing a file.
214
// so retry few times before we fall back to copy.
215
for( int retry=0; retry<5; retry++ ) {
216                         if(oldRoot.renameTo(newRoot)) {
217                             renamed = true;
218                             break; // succeeded
219
}
220                         try {
221                             Thread.sleep(500);
222                         } catch (InterruptedException JavaDoc e) {
223                             // process the interruption later
224
interrupted = true;
225                         }
226                     }
227
228                     if(interrupted)
229                         Thread.currentThread().interrupt();
230
231                     if(!renamed) {
232                         // failed to rename. it must be that some lengthy process is going on
233
// to prevent a rename operation. So do a copy. Ideally we'd like to
234
// later delete the old copy, but we can't reliably do so, as before the VM
235
// shuts down there might be a new job created under the old name.
236
Copy cp = new Copy();
237                         cp.setProject(new org.apache.tools.ant.Project());
238                         cp.setTodir(newRoot);
239                         FileSet src = new FileSet();
240                         src.setDir(getRootDir());
241                         cp.addFileset(src);
242                         cp.setOverwrite(true);
243                         cp.setPreserveLastModified(true);
244                         cp.setFailOnError(false); // keep going even if there's an error
245
cp.execute();
246
247                         // try to delete as much as possible
248
try {
249                             Util.deleteRecursive(oldRoot);
250                         } catch (IOException JavaDoc e) {
251                             // but ignore the error, since we expect that
252
e.printStackTrace();
253                         }
254                     }
255                 }
256
257                 parent.onRenamed((TopLevelItem)this,oldName,newName);
258
259                 // update BuildTrigger of other projects that point to this object.
260
// can't we generalize this?
261
for( Project p : parent.getProjects() ) {
262                     BuildTrigger t = (BuildTrigger) p.getPublishers().get(BuildTrigger.DESCRIPTOR);
263                     if(t!=null) {
264                         if(t.onJobRenamed(oldName,newName))
265                             p.save();
266                     }
267                 }
268             }
269         }
270     }
271
272     /**
273      * Returns true if we should display "build now" icon
274      */

275     public abstract boolean isBuildable();
276
277     /**
278      * Gets all the builds.
279      *
280      * @return
281      * never null. The first entry is the latest buildCommand.
282      */

283     public List<RunT> getBuilds() {
284         return new ArrayList JavaDoc<RunT>(_getRuns().values());
285     }
286
287     /**
288      * Gets all the builds in a map.
289      */

290     public SortedMap JavaDoc<Integer JavaDoc,RunT> getBuildsAsMap() {
291         return Collections.unmodifiableSortedMap(_getRuns());
292     }
293
294     /**
295      * @deprecated
296      * This is only used to support backward compatibility with
297      * old URLs.
298      */

299     @Deprecated JavaDoc
300     public RunT getBuild(String JavaDoc id) {
301         for (RunT r : _getRuns().values()) {
302             if(r.getId().equals(id))
303                 return r;
304         }
305         return null;
306     }
307
308     /**
309      * @param n
310      * The build number.
311      * @see Run#getNumber()
312      */

313     public RunT getBuildByNumber(int n) {
314         return _getRuns().get(n);
315     }
316
317     /**
318      * Gets the youngest build #m that satisfies <tt>n&lt;=m</tt>.
319      *
320      * This is useful when you'd like to fetch a build but the exact build might be already
321      * gone (deleted, rotated, etc.)
322      */

323     public RunT getNearestBuild(int n) {
324         SortedMap JavaDoc<Integer JavaDoc, ? extends RunT> m = _getRuns().tailMap(n);
325         if(m.isEmpty()) return null;
326         return m.get(m.firstKey());
327     }
328
329     public Object JavaDoc getDynamic(String JavaDoc token, StaplerRequest req, StaplerResponse rsp) {
330         try {
331             // try to interpret the token as build number
332
return _getRuns().get(Integer.valueOf(token));
333         } catch (NumberFormatException JavaDoc e) {
334             return super.getDynamic(token,req,rsp);
335         }
336     }
337
338     /**
339      * Directory for storing {@link Run} records.
340      * <p>
341      * Some {@link Job}s may not have backing data store for {@link Run}s,
342      * but those {@link Job}s that use file system for storing data
343      * should use this directory for consistency.
344      *
345      * @see RunMap
346      */

347     protected File getBuildDir() {
348         return new File(getRootDir(),"builds");
349     }
350
351     /**
352      * Gets all the runs.
353      *
354      * The resulting map must be immutable (by employing copy-on-write semantics.)
355      */

356     protected abstract SortedMap JavaDoc<Integer JavaDoc,? extends RunT> _getRuns();
357
358     /**
359      * Called from {@link Run} to remove it from this job.
360      *
361      * The files are deleted already. So all the callee needs to do
362      * is to remove a reference from this {@link Job}.
363      */

364     protected abstract void removeRun(RunT run);
365
366     /**
367      * Returns the last build.
368      */

369     public RunT getLastBuild() {
370         SortedMap JavaDoc<Integer JavaDoc,? extends RunT> runs = _getRuns();
371
372         if(runs.isEmpty()) return null;
373         return runs.get(runs.firstKey());
374     }
375
376     /**
377      * Returns the oldest build in the record.
378      */

379     public RunT getFirstBuild() {
380         SortedMap JavaDoc<Integer JavaDoc,? extends RunT> runs = _getRuns();
381
382         if(runs.isEmpty()) return null;
383         return runs.get(runs.lastKey());
384     }
385
386     /**
387      * Returns the last successful build, if any. Otherwise null.
388      * A stable build would include either {@link Result#SUCCESS} or {@link Result#UNSTABLE}.
389      * @see #getLastStableBuild()
390      */

391     public RunT getLastSuccessfulBuild() {
392         RunT r = getLastBuild();
393         // temporary hack till we figure out what's causing this bug
394
while(r!=null && (r.isBuilding() || r.getResult()==null || r.getResult().isWorseThan(Result.UNSTABLE)))
395             r=r.getPreviousBuild();
396         return r;
397     }
398
399     /**
400      * Returns the last stable build, if any. Otherwise null.
401      */

402     public RunT getLastStableBuild() {
403         RunT r = getLastBuild();
404         while(r!=null && (r.isBuilding() || r.getResult().isWorseThan(Result.SUCCESS)))
405             r=r.getPreviousBuild();
406         return r;
407     }
408
409     /**
410      * Returns the last failed build, if any. Otherwise null.
411      */

412     public RunT getLastFailedBuild() {
413         RunT r = getLastBuild();
414         while(r!=null && (r.isBuilding() || r.getResult()!=Result.FAILURE))
415             r=r.getPreviousBuild();
416         return r;
417     }
418
419     /**
420      * Used as the color of the status ball for the project.
421      */

422     public BallColor getIconColor() {
423         RunT lastBuild = getLastBuild();
424         while(lastBuild!=null && lastBuild.hasntStartedYet())
425             lastBuild = lastBuild.getPreviousBuild();
426
427         if(lastBuild!=null)
428             return lastBuild.getIconColor();
429         else
430             return BallColor.GREY;
431     }
432
433
434
435
436
437 //
438
//
439
// actions
440
//
441
//
442
/**
443      * Accepts submission from the configuration page.
444      */

445     public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
446         if(!Hudson.adminCheck(req,rsp))
447             return;
448
449         req.setCharacterEncoding("UTF-8");
450
451         description = req.getParameter("description");
452
453         if(req.getParameter("logrotate")!=null)
454             logRotator = LogRotator.DESCRIPTOR.newInstance(req);
455         else
456             logRotator = null;
457
458         keepDependencies = req.getParameter("keepDependencies")!=null;
459
460         save();
461
462         String JavaDoc newName = req.getParameter("name");
463         if(newName!=null && !newName.equals(name)) {
464             rsp.sendRedirect("rename?newName="+newName);
465         } else {
466             rsp.sendRedirect(".");
467         }
468     }
469
470     /**
471      * Returns the image that shows the current buildCommand status.
472      */

473     public void doBuildStatus( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
474         rsp.sendRedirect2(req.getContextPath()+"/nocacheImages/48x48/"+getBuildStatusUrl());
475     }
476
477     public String JavaDoc getBuildStatusUrl() {
478         return getIconColor()+".gif";
479     }
480
481     /**
482      * Returns the graph that shows how long each build took.
483      */

484     public void doBuildTimeGraph( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
485         if(getLastBuild()==null) {
486             rsp.setStatus(HttpServletResponse.SC_NOT_FOUND);
487             return;
488         }
489         if(req.checkIfModified(getLastBuild().getTimestamp(),rsp))
490             return;
491         ChartUtil.generateGraph(req,rsp, createBuildTimeTrendChart(),500,400);
492     }
493
494     /**
495      * Returns the clickable map for the build time graph.
496      * Loaded lazily by AJAX.
497      */

498     public void doBuildTimeGraphMap( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
499         if(getLastBuild()==null) {
500             rsp.setStatus(HttpServletResponse.SC_NOT_FOUND);
501             return;
502         }
503         if(req.checkIfModified(getLastBuild().getTimestamp(),rsp))
504             return;
505         ChartUtil.generateClickableMap(req,rsp, createBuildTimeTrendChart(),500,400);
506     }
507
508     private JFreeChart createBuildTimeTrendChart() {
509         class Label implements Comparable JavaDoc<Label> {
510             final Run run;
511
512             public Label(Run r) {
513                 this.run = r;
514             }
515
516             public int compareTo(Label that) {
517                 return this.run.number-that.run.number;
518             }
519
520             public boolean equals(Object JavaDoc o) {
521                 Label that = (Label) o;
522                 return run ==that.run;
523             }
524
525             public Color JavaDoc getColor() {
526                 // TODO: consider gradation. See http://www.javadrive.jp/java2d/shape/index9.html
527
Result r = run.getResult();
528                 if(r ==Result.FAILURE || r== Result.ABORTED)
529                     return ColorPalette.RED;
530                 else
531                     return ColorPalette.BLUE;
532             }
533
534             public int hashCode() {
535                 return run.hashCode();
536             }
537
538             public String JavaDoc toString() {
539                 String JavaDoc l = run.getDisplayName();
540                 if(run instanceof Build) {
541                     String JavaDoc s = ((Build)run).getBuiltOnStr();
542                     if(s!=null)
543                         l += ' '+s;
544                 }
545                 return l;
546             }
547
548         }
549
550         DataSetBuilder<String JavaDoc,Label> data = new DataSetBuilder<String JavaDoc, Label>();
551         for( Run r : getBuilds() )
552             data.add( ((double)r.getDuration())/(1000*60), "mins", new Label(r));
553
554         final CategoryDataset dataset = data.build();
555
556         final JFreeChart chart = ChartFactory.createStackedAreaChart(
557             null, // chart title
558
null, // unused
559
"min", // range axis label
560
dataset, // data
561
PlotOrientation.VERTICAL, // orientation
562
false, // include legend
563
true, // tooltips
564
false // urls
565
);
566
567         chart.setBackgroundPaint(Color.white);
568
569         final CategoryPlot plot = chart.getCategoryPlot();
570
571         // plot.setAxisOffset(new Spacer(Spacer.ABSOLUTE, 5.0, 5.0, 5.0, 5.0));
572
plot.setBackgroundPaint(Color.WHITE);
573         plot.setOutlinePaint(null);
574         plot.setForegroundAlpha(0.8f);
575 // plot.setDomainGridlinesVisible(true);
576
// plot.setDomainGridlinePaint(Color.white);
577
plot.setRangeGridlinesVisible(true);
578         plot.setRangeGridlinePaint(Color.black);
579
580         CategoryAxis domainAxis = new ShiftedCategoryAxis(null);
581         plot.setDomainAxis(domainAxis);
582         domainAxis.setCategoryLabelPositions(CategoryLabelPositions.UP_90);
583         domainAxis.setLowerMargin(0.0);
584         domainAxis.setUpperMargin(0.0);
585         domainAxis.setCategoryMargin(0.0);
586
587         final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
588         rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
589
590         StackedAreaRenderer ar = new StackedAreaRenderer2() {
591             @Override JavaDoc
592             public Paint JavaDoc getItemPaint(int row, int column) {
593                 Label key = (Label) dataset.getColumnKey(column);
594                 return key.getColor();
595             }
596
597             @Override JavaDoc
598             public String JavaDoc generateURL(CategoryDataset dataset, int row, int column) {
599                 Label label = (Label) dataset.getColumnKey(column);
600                 return String.valueOf(label.run.number);
601             }
602
603             @Override JavaDoc
604             public String JavaDoc generateToolTip(CategoryDataset dataset, int row, int column) {
605                 Label label = (Label) dataset.getColumnKey(column);
606                 return label.run.getDisplayName() + " : " + label.run.getDurationString();
607             }
608         };
609         plot.setRenderer(ar);
610
611         // crop extra space around the graph
612
plot.setInsets(new RectangleInsets(0,0,0,5.0));
613
614         return chart;
615     }
616
617     /**
618      * Renames this job.
619      */

620     public /*not synchronized. see renameTo()*/ void doDoRename( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
621         if(!Hudson.adminCheck(req,rsp))
622             return;
623
624         String JavaDoc newName = req.getParameter("newName");
625
626         renameTo(newName);
627         rsp.sendRedirect2(req.getContextPath()+'/'+getUrl()); // send to the new job page
628
}
629
630     public void doRssAll( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
631         rss(req, rsp, " all builds", new RunList(this));
632     }
633     public void doRssFailed( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
634         rss(req, rsp, " failed builds", new RunList(this).failureOnly());
635     }
636
637     private void rss(StaplerRequest req, StaplerResponse rsp, String JavaDoc suffix, RunList runs) throws IOException JavaDoc, ServletException JavaDoc {
638         RSS.forwardToRss(getDisplayName()+ suffix, getUrl(),
639             runs.newBuilds(), Run.FEED_ADAPTER, req, rsp );
640     }
641 }
642
Popular Tags