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 ; 32 import javax.servlet.http.HttpServletResponse ; 33 import java.awt.Color ; 34 import java.awt.Paint ; 35 import java.io.File ; 36 import java.io.IOException ; 37 import java.util.ArrayList ; 38 import java.util.Collection ; 39 import java.util.Collections ; 40 import java.util.List ; 41 import java.util.Map ; 42 import java.util.SortedMap ; 43 44 52 public abstract class Job<JobT extends Job<JobT,RunT>, RunT extends Run<JobT,RunT>> 53 extends AbstractItem implements ExtensionPoint { 54 55 64 protected transient int nextBuildNumber = 1; 65 66 private LogRotator logRotator; 67 68 private boolean keepDependencies; 69 70 73 private CopyOnWriteList<JobProperty<? super JobT>> properties = new CopyOnWriteList<JobProperty<? super JobT>>(); 74 75 protected Job(ItemGroup parent,String name) { 76 super(parent,name); 77 getBuildDir().mkdirs(); 78 } 79 80 public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException { 81 super.onLoad(parent, name); 82 83 TextFile f = getNextBuildNumberFile(); 84 if(f.exists()) { 85 try { 89 this.nextBuildNumber = Integer.parseInt(f.readTrim()); 90 } catch (NumberFormatException e) { 91 throw new IOException2(f+" doesn't contain a number",e); 92 } 93 } else { 94 saveNextBuildNumber(); 96 save(); } 98 99 if(properties==null) properties = new CopyOnWriteList<JobProperty<? super JobT>>(); 101 } 102 103 @Override 104 public void onCopiedFrom(Item src) { 105 super.onCopiedFrom(src); 106 this.nextBuildNumber = 1; } 108 109 private TextFile getNextBuildNumberFile() { 110 return new TextFile(new File(this.getRootDir(),"nextBuildNumber")); 111 } 112 113 protected void saveNextBuildNumber() throws IOException { 114 getNextBuildNumberFile().write(String.valueOf(nextBuildNumber)+'\n'); 115 } 116 117 public boolean isInQueue() { 118 return false; 119 } 120 121 124 public boolean isKeepDependencies() { 125 return keepDependencies; 126 } 127 128 131 public synchronized int assignBuildNumber() throws IOException { 132 int r = nextBuildNumber++; 133 saveNextBuildNumber(); 134 return r; 135 } 136 137 140 public int getNextBuildNumber() { 141 return nextBuildNumber; 142 } 143 144 147 public LogRotator getLogRotator() { 148 return logRotator; 149 } 150 151 public void setLogRotator(LogRotator logRotator) { 152 this.logRotator = logRotator; 153 } 154 155 public Collection <? extends Job> getAllJobs() { 156 return Collections.<Job>singleton(this); 157 } 158 159 162 @SuppressWarnings ("unchecked") 163 public Map <JobPropertyDescriptor,JobProperty<? super JobT>> getProperties() { 164 return Descriptor.toMap((Iterable )properties); 165 } 166 167 170 public <T extends JobProperty> T getProperty(Class <T> clazz) { 171 for (JobProperty p : properties) { 172 if(clazz.isInstance(p)) 173 return clazz.cast(p); 174 } 175 return null; 176 } 177 178 185 public void renameTo(String newName) throws IOException { 186 final Hudson parent = Hudson.getInstance(); 188 assert this instanceof TopLevelItem; 189 synchronized(parent) { 190 synchronized(this) { 191 if(newName==null) 193 throw new IllegalArgumentException ("New name is not given"); 194 if(parent.getItem(newName)!=null) 195 throw new IllegalArgumentException ("Job "+newName+" already exists"); 196 197 if(this.name.equals(newName)) 199 return; 200 201 202 String oldName = this.name; 203 File oldRoot = this.getRootDir(); 204 205 doSetName(newName); 206 File newRoot = this.getRootDir(); 207 208 { boolean interrupted=false; 210 boolean renamed = false; 211 212 for( int retry=0; retry<5; retry++ ) { 216 if(oldRoot.renameTo(newRoot)) { 217 renamed = true; 218 break; } 220 try { 221 Thread.sleep(500); 222 } catch (InterruptedException e) { 223 interrupted = true; 225 } 226 } 227 228 if(interrupted) 229 Thread.currentThread().interrupt(); 230 231 if(!renamed) { 232 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); cp.execute(); 246 247 try { 249 Util.deleteRecursive(oldRoot); 250 } catch (IOException e) { 251 e.printStackTrace(); 253 } 254 } 255 } 256 257 parent.onRenamed((TopLevelItem)this,oldName,newName); 258 259 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 275 public abstract boolean isBuildable(); 276 277 283 public List<RunT> getBuilds() { 284 return new ArrayList <RunT>(_getRuns().values()); 285 } 286 287 290 public SortedMap <Integer ,RunT> getBuildsAsMap() { 291 return Collections.unmodifiableSortedMap(_getRuns()); 292 } 293 294 299 @Deprecated 300 public RunT getBuild(String id) { 301 for (RunT r : _getRuns().values()) { 302 if(r.getId().equals(id)) 303 return r; 304 } 305 return null; 306 } 307 308 313 public RunT getBuildByNumber(int n) { 314 return _getRuns().get(n); 315 } 316 317 323 public RunT getNearestBuild(int n) { 324 SortedMap <Integer , ? extends RunT> m = _getRuns().tailMap(n); 325 if(m.isEmpty()) return null; 326 return m.get(m.firstKey()); 327 } 328 329 public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) { 330 try { 331 return _getRuns().get(Integer.valueOf(token)); 333 } catch (NumberFormatException e) { 334 return super.getDynamic(token,req,rsp); 335 } 336 } 337 338 347 protected File getBuildDir() { 348 return new File(getRootDir(),"builds"); 349 } 350 351 356 protected abstract SortedMap <Integer ,? extends RunT> _getRuns(); 357 358 364 protected abstract void removeRun(RunT run); 365 366 369 public RunT getLastBuild() { 370 SortedMap <Integer ,? extends RunT> runs = _getRuns(); 371 372 if(runs.isEmpty()) return null; 373 return runs.get(runs.firstKey()); 374 } 375 376 379 public RunT getFirstBuild() { 380 SortedMap <Integer ,? extends RunT> runs = _getRuns(); 381 382 if(runs.isEmpty()) return null; 383 return runs.get(runs.lastKey()); 384 } 385 386 391 public RunT getLastSuccessfulBuild() { 392 RunT r = getLastBuild(); 393 while(r!=null && (r.isBuilding() || r.getResult()==null || r.getResult().isWorseThan(Result.UNSTABLE))) 395 r=r.getPreviousBuild(); 396 return r; 397 } 398 399 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 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 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 445 public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException , ServletException { 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 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 473 public void doBuildStatus( StaplerRequest req, StaplerResponse rsp ) throws IOException { 474 rsp.sendRedirect2(req.getContextPath()+"/nocacheImages/48x48/"+getBuildStatusUrl()); 475 } 476 477 public String getBuildStatusUrl() { 478 return getIconColor()+".gif"; 479 } 480 481 484 public void doBuildTimeGraph( StaplerRequest req, StaplerResponse rsp ) throws IOException { 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 498 public void doBuildTimeGraphMap( StaplerRequest req, StaplerResponse rsp ) throws IOException { 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 <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 o) { 521 Label that = (Label) o; 522 return run ==that.run; 523 } 524 525 public Color getColor() { 526 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 toString() { 539 String l = run.getDisplayName(); 540 if(run instanceof Build) { 541 String s = ((Build)run).getBuiltOnStr(); 542 if(s!=null) 543 l += ' '+s; 544 } 545 return l; 546 } 547 548 } 549 550 DataSetBuilder<String ,Label> data = new DataSetBuilder<String , 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, null, "min", dataset, PlotOrientation.VERTICAL, false, true, false ); 566 567 chart.setBackgroundPaint(Color.white); 568 569 final CategoryPlot plot = chart.getCategoryPlot(); 570 571 plot.setBackgroundPaint(Color.WHITE); 573 plot.setOutlinePaint(null); 574 plot.setForegroundAlpha(0.8f); 575 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 592 public Paint getItemPaint(int row, int column) { 593 Label key = (Label) dataset.getColumnKey(column); 594 return key.getColor(); 595 } 596 597 @Override 598 public String 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 604 public String 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 plot.setInsets(new RectangleInsets(0,0,0,5.0)); 613 614 return chart; 615 } 616 617 620 public void doDoRename( StaplerRequest req, StaplerResponse rsp ) throws IOException { 621 if(!Hudson.adminCheck(req,rsp)) 622 return; 623 624 String newName = req.getParameter("newName"); 625 626 renameTo(newName); 627 rsp.sendRedirect2(req.getContextPath()+'/'+getUrl()); } 629 630 public void doRssAll( StaplerRequest req, StaplerResponse rsp ) throws IOException , ServletException { 631 rss(req, rsp, " all builds", new RunList(this)); 632 } 633 public void doRssFailed( StaplerRequest req, StaplerResponse rsp ) throws IOException , ServletException { 634 rss(req, rsp, " failed builds", new RunList(this).failureOnly()); 635 } 636 637 private void rss(StaplerRequest req, StaplerResponse rsp, String suffix, RunList runs) throws IOException , ServletException { 638 RSS.forwardToRss(getDisplayName()+ suffix, getUrl(), 639 runs.newBuilds(), Run.FEED_ADAPTER, req, rsp ); 640 } 641 } 642 | Popular Tags |