KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > hudson > model > Run


1 package hudson.model;
2
3 import com.thoughtworks.xstream.XStream;
4 import hudson.CloseProofOutputStream;
5 import hudson.ExtensionPoint;
6 import hudson.FeedAdapter;
7 import hudson.FilePath;
8 import hudson.Util;
9 import static hudson.Util.combine;
10 import hudson.XmlFile;
11 import hudson.tasks.BuildStep;
12 import hudson.tasks.LogRotator;
13 import hudson.tasks.test.AbstractTestResultAction;
14 import hudson.util.IOException2;
15 import hudson.util.XStream2;
16 import org.kohsuke.stapler.StaplerRequest;
17 import org.kohsuke.stapler.StaplerResponse;
18
19 import javax.servlet.ServletException JavaDoc;
20 import javax.servlet.http.HttpServletResponse JavaDoc;
21 import java.io.File JavaDoc;
22 import java.io.FileOutputStream JavaDoc;
23 import java.io.IOException JavaDoc;
24 import java.io.PrintStream JavaDoc;
25 import java.io.PrintWriter JavaDoc;
26 import java.io.Writer JavaDoc;
27 import java.text.ParseException JavaDoc;
28 import java.text.SimpleDateFormat JavaDoc;
29 import java.util.ArrayList JavaDoc;
30 import java.util.Calendar JavaDoc;
31 import java.util.Comparator JavaDoc;
32 import java.util.GregorianCalendar JavaDoc;
33 import java.util.HashMap JavaDoc;
34 import java.util.List JavaDoc;
35 import java.util.Map JavaDoc;
36 import java.util.logging.Logger JavaDoc;
37
38 /**
39  * A particular execution of {@link Job}.
40  *
41  * <p>
42  * Custom {@link Run} type is always used in conjunction with
43  * a custom {@link Job} type, so there's no separate registration
44  * mechanism for custom {@link Run} types.
45  *
46  * @author Kohsuke Kawaguchi
47  */

48 public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,RunT>>
49         extends Actionable implements ExtensionPoint, Comparable JavaDoc<RunT> {
50
51     protected transient final JobT project;
52
53     /**
54      * Build number.
55      *
56      * <p>
57      * In earlier versions &lt; 1.24, this number is not unique nor continuous,
58      * but going forward, it will, and this really replaces the build id.
59      */

60     public /*final*/ int number;
61
62     /**
63      * Previous build. Can be null.
64      * These two fields are maintained and updated by {@link RunMap}.
65      */

66     protected volatile transient RunT previousBuild;
67     /**
68      * Next build. Can be null.
69      */

70     protected volatile transient RunT nextBuild;
71
72     /**
73      * When the build is scheduled.
74      */

75     protected transient final Calendar JavaDoc timestamp;
76
77     /**
78      * The build result.
79      * This value may change while the state is in {@link State#BUILDING}.
80      */

81     protected volatile Result result;
82
83     /**
84      * Human-readable description. Can be null.
85      */

86     protected volatile String JavaDoc description;
87
88     /**
89      * The current build state.
90      */

91     protected volatile transient State state;
92
93     private static enum State {
94         NOT_STARTED,
95         BUILDING,
96         COMPLETED
97     }
98
99     /**
100      * Number of milli-seconds it took to run this build.
101      */

102     protected long duration;
103
104     /**
105      * Keeps this log entries.
106      */

107     private boolean keepLog;
108
109     protected static final SimpleDateFormat JavaDoc ID_FORMATTER = new SimpleDateFormat JavaDoc("yyyy-MM-dd_HH-mm-ss");
110
111     /**
112      * Creates a new {@link Run}.
113      */

114     protected Run(JobT job) throws IOException JavaDoc {
115         this(job, new GregorianCalendar JavaDoc());
116         this.number = project.assignBuildNumber();
117     }
118
119     /**
120      * Constructor for creating a {@link Run} object in
121      * an arbitrary state.
122      */

123     protected Run(JobT job, Calendar JavaDoc timestamp) {
124         this.project = job;
125         this.timestamp = timestamp;
126         this.state = State.NOT_STARTED;
127     }
128
129     /**
130      * Loads a run from a log file.
131      */

132     protected Run(JobT project, File buildDir) throws IOException JavaDoc {
133         this(project, new GregorianCalendar JavaDoc());
134         try {
135             this.timestamp.setTime(ID_FORMATTER.parse(buildDir.getName()));
136         } catch (ParseException JavaDoc e) {
137             throw new IOException2("Invalid directory name "+buildDir,e);
138         } catch (NumberFormatException JavaDoc e) {
139             throw new IOException2("Invalid directory name "+buildDir,e);
140         }
141         this.state = State.COMPLETED;
142         this.result = Result.FAILURE; // defensive measure. value should be overwritten by unmarshal, but just in case the saved data is inconsistent
143
getDataFile().unmarshal(this); // load the rest of the data
144
}
145
146
147     /**
148      * Ordering based on build numbers.
149      */

150     public int compareTo(RunT that) {
151         return this.number - that.number;
152     }
153
154     /**
155      * Returns the build result.
156      *
157      * <p>
158      * When a build is {@link #isBuilding() in progress}, this method
159      * may return null or a temporary intermediate result.
160      */

161     public final Result getResult() {
162         return result;
163     }
164
165     public void setResult(Result r) {
166         // state can change only when we are building
167
assert state==State.BUILDING;
168
169         StackTraceElement JavaDoc caller = findCaller(Thread.currentThread().getStackTrace(),"setResult");
170
171
172         // result can only get worse
173
if(result==null) {
174             result = r;
175             LOGGER.info(toString()+" : result is set to "+r+" by "+caller);
176         } else {
177             if(r.isWorseThan(result)) {
178                 LOGGER.info(toString()+" : result is set to "+r+" by "+caller);
179                 result = r;
180             }
181         }
182     }
183
184     private StackTraceElement JavaDoc findCaller(StackTraceElement JavaDoc[] stackTrace, String JavaDoc callee) {
185         for(int i=0; i<stackTrace.length-1; i++) {
186             StackTraceElement JavaDoc e = stackTrace[i];
187             if(e.getMethodName().equals(callee))
188                 return stackTrace[i+1];
189         }
190         return null; // not found
191
}
192
193     /**
194      * Returns true if the build is not completed yet.
195      */

196     public boolean isBuilding() {
197         return state!=State.COMPLETED;
198     }
199
200     /**
201      * Gets the {@link Executor} building this job, if it's being built.
202      * Otherwise null.
203      */

204     public Executor getExecutor() {
205         for( Computer c : Hudson.getInstance().getComputers() ) {
206             for (Executor e : c.getExecutors()) {
207                 if(e.getCurrentBuild()==(Object JavaDoc)this)
208                     return e;
209             }
210         }
211         return null;
212     }
213
214     /**
215      * Returns true if this log file should be kept and not deleted.
216      *
217      * This is used as a signal to the {@link LogRotator}.
218      */

219     public final boolean isKeepLog() {
220         return getWhyKeepLog()!=null;
221     }
222
223     /**
224      * If {@link #isKeepLog()} returns true, returns a human readable
225      * one-line string that explains why it's being kept.
226      */

227     public String JavaDoc getWhyKeepLog() {
228         if(keepLog)
229             return "explicitly marked to keep the record";
230         return null; // not marked at all
231
}
232
233     /**
234      * The project this build is for.
235      */

236     public JobT getParent() {
237         return project;
238     }
239
240     /**
241      * When the build is scheduled.
242      */

243     public Calendar JavaDoc getTimestamp() {
244         return timestamp;
245     }
246
247     public String JavaDoc getDescription() {
248         return description;
249     }
250
251     /**
252      * Returns the length-limited description.
253      * @return The length-limited description.
254      */

255     public String JavaDoc getTruncatedDescription() {
256         final int maxDescrLength = 100;
257         if (description == null || description.length() < maxDescrLength) {
258             return description;
259         }
260
261         final String JavaDoc ending = "...";
262         
263         // limit the description
264
String JavaDoc truncDescr = description.substring(
265                 0, maxDescrLength - ending.length());
266
267         // truncate the description on the space
268
int lastSpace = truncDescr.lastIndexOf(" ");
269         if (lastSpace != -1) {
270             truncDescr = truncDescr.substring(0, lastSpace);
271         }
272
273         return truncDescr + ending;
274     }
275
276     /**
277      * Gets the string that says how long since this build has scheduled.
278      *
279      * @return
280      * string like "3 minutes" "1 day" etc.
281      */

282     public String JavaDoc getTimestampString() {
283         long duration = new GregorianCalendar JavaDoc().getTimeInMillis()-timestamp.getTimeInMillis();
284         return Util.getTimeSpanString(duration);
285     }
286
287     /**
288      * Returns the timestamp formatted in xs:dateTime.
289      */

290     public String JavaDoc getTimestampString2() {
291         return Util.XS_DATETIME_FORMATTER.format(timestamp.getTime());
292     }
293
294     /**
295      * Gets the string that says how long the build took to run.
296      */

297     public String JavaDoc getDurationString() {
298         return Util.getTimeSpanString(duration);
299     }
300
301     /**
302      * Gets the millisecond it took to build.
303      */

304     public long getDuration() {
305         return duration;
306     }
307
308     /**
309      * Gets the icon color for display.
310      */

311     public BallColor getIconColor() {
312         if(!isBuilding()) {
313             // already built
314
if(result==Result.SUCCESS)
315                 return BallColor.BLUE;
316             if(result== Result.UNSTABLE)
317                 return BallColor.YELLOW;
318             else
319                 return BallColor.RED;
320         }
321
322         // a new build is in progress
323
BallColor baseColor;
324         if(previousBuild==null)
325             baseColor = BallColor.GREY;
326         else
327             baseColor = previousBuild.getIconColor();
328
329         return baseColor.anime();
330     }
331
332     /**
333      * Returns true if the build is still queued and hasn't started yet.
334      */

335     public boolean hasntStartedYet() {
336         return state ==State.NOT_STARTED;
337     }
338
339     public String JavaDoc toString() {
340         return project.getName()+" #"+number;
341     }
342
343     public String JavaDoc getDisplayName() {
344         return "#"+number;
345     }
346
347     public int getNumber() {
348         return number;
349     }
350
351     public RunT getPreviousBuild() {
352         return previousBuild;
353     }
354
355     /**
356      * Returns the last build that didn't fail before this build.
357      */

358     public RunT getPreviousNotFailedBuild() {
359         RunT r=previousBuild;
360         while( r!=null && r.getResult()==Result.FAILURE )
361             r=r.previousBuild;
362         return r;
363     }
364
365     /**
366      * Returns the last failed build before this build.
367      */

368     public RunT getPreviousFailedBuild() {
369         RunT r=previousBuild;
370         while( r!=null && r.getResult()!=Result.FAILURE )
371             r=r.previousBuild;
372         return r;
373     }
374
375     public RunT getNextBuild() {
376         return nextBuild;
377     }
378
379     // I really messed this up. I'm hoping to fix this some time
380
// it shouldn't have trailing '/', and instead it should have leading '/'
381
public String JavaDoc getUrl() {
382         return project.getUrl()+getNumber()+'/';
383     }
384
385     /**
386      * Unique ID of this build.
387      */

388     public String JavaDoc getId() {
389         return ID_FORMATTER.format(timestamp.getTime());
390     }
391
392     /**
393      * Root directory of this {@link Run} on the master.
394      *
395      * Files related to this {@link Run} should be stored below this directory.
396      */

397     public File getRootDir() {
398         File f = new File(project.getBuildDir(),getId());
399         f.mkdirs();
400         return f;
401     }
402
403     /**
404      * Gets the directory where the artifacts are archived.
405      */

406     public File getArtifactsDir() {
407         return new File(getRootDir(),"archive");
408     }
409
410     /**
411      * Gets the first {@value #CUTOFF} artifacts (relative to {@link #getArtifactsDir()}.
412      */

413     public List JavaDoc<Artifact> getArtifacts() {
414         List JavaDoc<Artifact> r = new ArrayList JavaDoc<Artifact>();
415         addArtifacts(getArtifactsDir(),"",r);
416         return r;
417     }
418
419     /**
420      * Returns true if this run has any artifacts.
421      *
422      * <p>
423      * The strange method name is so that we can access it from EL.
424      */

425     public boolean getHasArtifacts() {
426         return !getArtifacts().isEmpty();
427     }
428
429     private void addArtifacts( File dir, String JavaDoc path, List JavaDoc<Artifact> r ) {
430         String JavaDoc[] children = dir.list();
431         if(children==null) return;
432         for (String JavaDoc child : children) {
433             if(r.size()>CUTOFF)
434                 return;
435             File sub = new File(dir, child);
436             if (sub.isDirectory()) {
437                 addArtifacts(sub, path + child + '/', r);
438             } else {
439                 r.add(new Artifact(path + child));
440             }
441         }
442     }
443
444     private static final int CUTOFF = 17; // 0, 1,... 16, and then "too many"
445

446     /**
447      * A build artifact.
448      */

449     public class Artifact {
450         /**
451          * Relative path name from {@link Run#getArtifactsDir()}
452          */

453         private final String JavaDoc relativePath;
454
455         private Artifact(String JavaDoc relativePath) {
456             this.relativePath = relativePath;
457         }
458
459         /**
460          * Gets the artifact file.
461          */

462         public File getFile() {
463             return new File(getArtifactsDir(),relativePath);
464         }
465
466         /**
467          * Returns just the file name portion, without the path.
468          */

469         public String JavaDoc getFileName() {
470             return getFile().getName();
471         }
472
473         public String JavaDoc toString() {
474             return relativePath;
475         }
476     }
477
478     /**
479      * Returns the log file.
480      */

481     public File getLogFile() {
482         return new File(getRootDir(),"log");
483     }
484
485     /**
486      * Deletes this build and its entire log
487      *
488      * @throws IOException
489      * if we fail to delete.
490      */

491     public synchronized void delete() throws IOException JavaDoc {
492         File rootDir = getRootDir();
493         File tmp = new File(rootDir.getParentFile(),'.'+rootDir.getName());
494
495         if(!rootDir.renameTo(tmp))
496             throw new IOException JavaDoc(rootDir+" is in use");
497
498         Util.deleteRecursive(tmp);
499
500         removeRunFromParent();
501     }
502     @SuppressWarnings JavaDoc("unchecked") // seems this is too clever for Java's type system?
503
private void removeRunFromParent() {
504         getParent().removeRun((RunT)this);
505     }
506
507     protected static interface Runner {
508         /**
509          * Performs the main build and returns the status code.
510          *
511          * @throws Exception
512          * exception will be recorded and the build will be considered a failure.
513          */

514         Result run( BuildListener listener ) throws Exception JavaDoc, RunnerAbortedException;
515
516         /**
517          * Performs the post-build action.
518          */

519         void post( BuildListener listener );
520     }
521
522     /**
523      * Used in {@link Runner#run} to indicates that a fatal error in a build
524      * is reported to {@link BuildListener} and the build should be simply aborted
525      * without further recording a stack trace.
526      */

527     public static final class RunnerAbortedException extends RuntimeException JavaDoc {}
528
529     protected final void run(Runner job) {
530         if(result!=null)
531             return; // already built.
532

533         onStartBuilding();
534         try {
535             // to set the state to COMPLETE in the end, even if the thread dies abnormally.
536
// otherwise the queue state becomes inconsistent
537

538             long start = System.currentTimeMillis();
539             BuildListener listener=null;
540             PrintStream JavaDoc log = null;
541
542             try {
543                 try {
544                     log = new PrintStream JavaDoc(new FileOutputStream JavaDoc(getLogFile()));
545                     listener = new StreamBuildListener(new CloseProofOutputStream(log));
546
547                     listener.started();
548
549                     setResult(job.run(listener));
550
551                     LOGGER.info(toString()+" main build action completed: "+result);
552                 } catch (ThreadDeath JavaDoc t) {
553                     throw t;
554                 } catch( RunnerAbortedException e ) {
555                     result = Result.FAILURE;
556                 } catch( Throwable JavaDoc e ) {
557                     handleFatalBuildProblem(listener,e);
558                     result = Result.FAILURE;
559                 }
560
561                 // even if the main build fails fatally, try to run post build processing
562
job.post(listener);
563
564             } catch (ThreadDeath JavaDoc t) {
565                 throw t;
566             } catch( Throwable JavaDoc e ) {
567                 handleFatalBuildProblem(listener,e);
568                 result = Result.FAILURE;
569             }
570
571             long end = System.currentTimeMillis();
572             duration = end-start;
573
574             if(listener!=null)
575                 listener.finished(result);
576             if(log!=null)
577                 log.close();
578
579             try {
580                 save();
581             } catch (IOException JavaDoc e) {
582                 e.printStackTrace();
583             }
584
585             try {
586                 LogRotator lr = getParent().getLogRotator();
587                 if(lr!=null)
588                     lr.perform(getParent());
589             } catch (IOException JavaDoc e) {
590                 e.printStackTrace();
591             }
592         } finally {
593             onEndBuilding();
594         }
595     }
596
597     /**
598      * Handles a fatal build problem (exception) that occurred during the build.
599      */

600     private void handleFatalBuildProblem(BuildListener listener, Throwable JavaDoc e) {
601         if(listener!=null) {
602             if(e instanceof IOException JavaDoc)
603                 Util.displayIOException((IOException JavaDoc)e,listener);
604
605             Writer JavaDoc w = listener.fatalError(e.getMessage());
606             if(w!=null) {
607                 try {
608                     e.printStackTrace(new PrintWriter JavaDoc(w));
609                     w.close();
610                 } catch (IOException JavaDoc e1) {
611                     // ignore
612
}
613             }
614         }
615     }
616
617     /**
618      * Called when a job started building.
619      */

620     protected void onStartBuilding() {
621         state = State.BUILDING;
622     }
623
624     /**
625      * Called when a job finished building normally or abnormally.
626      */

627     protected void onEndBuilding() {
628         state = State.COMPLETED;
629         if(result==null) {
630             // shouldn't happen, but be defensive until we figure out why
631
result = Result.FAILURE;
632             LOGGER.warning(toString()+": No build result is set, so marking as failure. This shouldn't happen");
633         }
634     }
635
636     /**
637      * Save the settings to a file.
638      */

639     public synchronized void save() throws IOException JavaDoc {
640         getDataFile().write(this);
641     }
642
643     private XmlFile getDataFile() {
644         return new XmlFile(XSTREAM,new File(getRootDir(),"build.xml"));
645     }
646
647     /**
648      * Gets the log of the build as a string.
649      *
650      * I know, this isn't terribly efficient!
651      */

652     public String JavaDoc getLog() throws IOException JavaDoc {
653         return Util.loadFile(getLogFile());
654     }
655
656     public void doBuildStatus( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
657         // see Hudson.doNocacheImages. this is a work around for a bug in Firefox
658
rsp.sendRedirect2(req.getContextPath()+"/nocacheImages/48x48/"+getBuildStatusUrl());
659     }
660
661     public String JavaDoc getBuildStatusUrl() {
662         return getIconColor()+".gif";
663     }
664
665     public static class Summary {
666         /**
667          * Is this build worse or better, compared to the previous build?
668          */

669         public boolean isWorse;
670         public String JavaDoc message;
671
672         public Summary(boolean worse, String JavaDoc message) {
673             this.isWorse = worse;
674             this.message = message;
675         }
676     }
677
678     /**
679      * Gets an object that computes the single line summary of this build.
680      */

681     public Summary getBuildStatusSummary() {
682         Run prev = getPreviousBuild();
683
684         if(getResult()==Result.SUCCESS) {
685             if(prev==null || prev.getResult()== Result.SUCCESS)
686                 return new Summary(false,"stable");
687             else
688                 return new Summary(false,"back to normal");
689         }
690
691         if(getResult()==Result.FAILURE) {
692             RunT since = getPreviousNotFailedBuild();
693             if(since==null)
694                 return new Summary(false,"broken for a long time");
695             if(since==prev)
696                 return new Summary(true,"broken since this build");
697             return new Summary(false,"broken since "+since.getDisplayName());
698         }
699
700         if(getResult()==Result.ABORTED)
701             return new Summary(false,"aborted");
702
703         if(getResult()==Result.UNSTABLE) {
704             if(((Run)this) instanceof Build) {
705                 AbstractTestResultAction trN = ((Build)(Run)this).getTestResultAction();
706                 AbstractTestResultAction trP = prev==null ? null : ((Build) prev).getTestResultAction();
707                 if(trP==null) {
708                     if(trN!=null && trN.getFailCount()>0)
709                         return new Summary(false,combine(trN.getFailCount(),"test failure"));
710                     else // ???
711
return new Summary(false,"unstable");
712                 }
713                 if(trP.getFailCount()==0)
714                     return new Summary(true,combine(trP.getFailCount(),"test")+" started to fail");
715                 if(trP.getFailCount() < trN.getFailCount())
716                     return new Summary(true,combine(trN.getFailCount()-trP.getFailCount(),"more test")
717                         +" are failing ("+trN.getFailCount()+" total)");
718                 if(trP.getFailCount() > trN.getFailCount())
719                     return new Summary(false,combine(trP.getFailCount()-trN.getFailCount(),"less test")
720                         +" are failing ("+trN.getFailCount()+" total)");
721
722                 return new Summary(false,combine(trN.getFailCount(),"test")+" are still failing");
723             }
724         }
725
726         return new Summary(false,"?");
727     }
728
729     /**
730      * Serves the artifacts.
731      */

732     public void doArtifact( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc, InterruptedException JavaDoc {
733         new DirectoryBrowserSupport(this).serveFile(req, rsp, new FilePath(getArtifactsDir()), "package.gif", true);
734     }
735
736     /**
737      * Returns the build number in the body.
738      */

739     public void doBuildNumber( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc {
740         rsp.setContentType("text/plain");
741         rsp.setCharacterEncoding("US-ASCII");
742         rsp.setStatus(HttpServletResponse.SC_OK);
743         rsp.getWriter().print(number);
744     }
745
746     /**
747      * Handles incremental log output.
748      */

749     public void doProgressiveLog( StaplerRequest req, StaplerResponse rsp) throws IOException JavaDoc {
750         new LargeText(getLogFile(),!isBuilding()).doProgressText(req,rsp);
751     }
752
753     public void doToggleLogKeep( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
754         if(!Hudson.adminCheck(req,rsp))
755             return;
756
757         keepLog = !keepLog;
758         save();
759         rsp.forwardToPreviousPage(req);
760     }
761
762     /**
763      * Marks this build to keep the log.
764      */

765     public void keepLog() throws IOException JavaDoc {
766         keepLog = true;
767         save();
768     }
769     
770     /**
771      * Deletes the build when the button is pressed.
772      */

773     public void doDoDelete( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
774         if(!Hudson.adminCheck(req,rsp))
775             return;
776         
777         // We should not simply delete the build if it has been explicitly
778
// marked to be preserved, or if the build should not be deleted
779
// due to dependencies!
780
String JavaDoc why = getWhyKeepLog();
781         if (why!=null) {
782             sendError("Unable to delete "+toString()+": "+why,req,rsp);
783             return;
784         }
785
786         delete();
787         rsp.sendRedirect2(req.getContextPath()+'/' + getParent().getUrl());
788     }
789
790     /**
791      * Accepts the new description.
792      */

793     public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException JavaDoc, ServletException JavaDoc {
794         if(!Hudson.adminCheck(req,rsp))
795             return;
796
797         req.setCharacterEncoding("UTF-8");
798         description = req.getParameter("description");
799         save();
800         rsp.sendRedirect("."); // go to the top page
801
}
802
803     /**
804      * Returns the map that contains environmental variables for this build.
805      *
806      * Used by {@link BuildStep}s that invoke external processes.
807      */

808     public Map JavaDoc<String JavaDoc,String JavaDoc> getEnvVars() {
809         Map JavaDoc<String JavaDoc,String JavaDoc> env = new HashMap JavaDoc<String JavaDoc,String JavaDoc>();
810         env.put("BUILD_NUMBER",String.valueOf(number));
811         env.put("BUILD_ID",getId());
812         env.put("BUILD_TAG","hudson-"+getParent().getName()+"-"+number);
813         env.put("JOB_NAME",getParent().getName());
814
815         Thread JavaDoc t = Thread.currentThread();
816         if (t instanceof Executor) {
817             Executor e = (Executor) t;
818             env.put("EXECUTOR_NUMBER",String.valueOf(e.getNumber()));
819         }
820
821         return env;
822     }
823
824     private static final XStream XSTREAM = new XStream2();
825     static {
826         XSTREAM.alias("build",Build.class);
827         XSTREAM.registerConverter(Result.conv);
828     }
829
830     private static final Logger JavaDoc LOGGER = Logger.getLogger(Run.class.getName());
831
832     /**
833      * Sort by date. Newer ones first.
834      */

835     public static final Comparator JavaDoc<Run> ORDER_BY_DATE = new Comparator JavaDoc<Run>() {
836         public int compare(Run lhs, Run rhs) {
837             long lt = lhs.getTimestamp().getTimeInMillis();
838             long rt = rhs.getTimestamp().getTimeInMillis();
839             if(lt>rt) return -1;
840             if(lt<rt) return 1;
841             return 0;
842         }
843     };
844
845     /**
846      * {@link FeedAdapter} to produce feed from the summary of this build.
847      */

848     public static final FeedAdapter<Run> FEED_ADAPTER = new FeedAdapter<Run>() {
849         public String JavaDoc getEntryTitle(Run entry) {
850             return entry+" ("+entry.getResult()+")";
851         }
852
853         public String JavaDoc getEntryUrl(Run entry) {
854             return entry.getUrl();
855         }
856
857         // produces a tag URL as per RFC 4151, required by Atom 1.0
858
public String JavaDoc getEntryID(Run entry) {
859             return "tag:" + "hudson.dev.java.net,"
860                 + entry.getTimestamp().get(Calendar.YEAR) + ":"
861                 + entry.getParent().getName()+':'+entry.getId();
862         }
863
864         public Calendar JavaDoc getEntryTimestamp(Run entry) {
865             return entry.getTimestamp();
866         }
867     };
868 }
869
Popular Tags