AbstractProject.java 55.0 KB
Newer Older
K
kohsuke 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * The MIT License
 * 
 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste Quenot, Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom Huybrechts, id:cactusman
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
24 25
package hudson.model;

K
kohsuke 已提交
26
import hudson.AbortException;
27
import hudson.FeedAdapter;
28
import hudson.FilePath;
29
import hudson.Launcher;
30
import hudson.Util;
31
import hudson.cli.declarative.CLIMethod;
K
kohsuke 已提交
32
import hudson.slaves.WorkspaceList;
M
mdonohue 已提交
33
import hudson.model.Cause.LegacyCodeCause;
34
import hudson.model.Cause.UserCause;
35
import hudson.model.Cause.RemoteCause;
36 37 38
import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.RunMap.Constructor;
39
import hudson.model.Queue.WaitingItem;
K
kohsuke 已提交
40
import hudson.model.Queue.Executable;
41
import hudson.model.queue.CauseOfBlockage;
K
kohsuke 已提交
42
import hudson.scm.ChangeLogSet;
K
kohsuke 已提交
43
import hudson.scm.ChangeLogSet.Entry;
44
import hudson.scm.NullSCM;
45
import hudson.scm.SCM;
K
kohsuke 已提交
46
import hudson.scm.SCMS;
J
jbq 已提交
47
import hudson.search.SearchIndexBuilder;
K
kohsuke 已提交
48
import hudson.security.Permission;
49
import hudson.tasks.BuildStep;
J
jbq 已提交
50
import hudson.tasks.BuildTrigger;
51
import hudson.tasks.Mailer;
52
import hudson.tasks.Publisher;
53
import hudson.tasks.BuildStepDescriptor;
K
kohsuke 已提交
54
import hudson.tasks.BuildWrapperDescriptor;
K
kohsuke 已提交
55
import hudson.triggers.SCMTrigger;
56
import hudson.triggers.Trigger;
57
import hudson.triggers.TriggerDescriptor;
58
import hudson.util.DescribableList;
59
import hudson.util.EditDistance;
S
 
shinodkm 已提交
60
import hudson.util.FormValidation;
K
kohsuke 已提交
61 62 63 64 65 66
import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
S
 
shinodkm 已提交
67
import org.kohsuke.stapler.QueryParameter;
68 69 70
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.ForwardToView;
71

K
kohsuke 已提交
72
import javax.servlet.ServletException;
73
import java.io.File;
74
import java.io.IOException;
K
kohsuke 已提交
75
import java.lang.reflect.InvocationTargetException;
K
kohsuke 已提交
76
import java.util.ArrayList;
77
import java.util.Arrays;
78
import java.util.Calendar;
79
import java.util.Collection;
J
jbq 已提交
80
import java.util.Collections;
81
import java.util.Comparator;
J
jbq 已提交
82
import java.util.HashSet;
83 84
import java.util.List;
import java.util.Map;
J
jbq 已提交
85
import java.util.Set;
86 87 88
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
K
kohsuke 已提交
89
import java.util.concurrent.Future;
90 91
import java.util.logging.Level;
import java.util.logging.Logger;
92 93 94

/**
 * Base implementation of {@link Job}s that build software.
95
 *
96
 * For now this is primarily the common part of {@link Project} and MavenModule.
97
 *
98 99 100
 * @author Kohsuke Kawaguchi
 * @see AbstractBuild
 */
101
public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Job<P,R> implements BuildableItem {
102

103
    /**
104 105 106
     * {@link SCM} associated with the project.
     * To allow derived classes to link {@link SCM} config to elsewhere,
     * access to this variable should always go through {@link #getScm()}.
107
     */
K
kohsuke 已提交
108
    private volatile SCM scm = new NullSCM();
109

110 111 112
    /**
     * All the builds keyed by their build number.
     */
113
    protected transient /*almost final*/ RunMap<R> builds = new RunMap<R>();
114 115 116 117

    /**
     * The quiet period. Null to delegate to the system default.
     */
K
kohsuke 已提交
118
    private volatile Integer quietPeriod = null;
S
 
shinodkm 已提交
119 120
    
    /**
121
     * The retry count. Null to delegate to the system default.
S
 
shinodkm 已提交
122
     */
123
    private volatile Integer scmCheckoutRetryCount = null;
124 125

    /**
126 127 128 129 130 131 132
     * If this project is configured to be only built on a certain label,
     * this value will be set to that label.
     *
     * For historical reasons, this is called 'assignedNode'. Also for
     * a historical reason, null to indicate the affinity
     * with the master node.
     *
133
     * @see #canRoam
134 135 136 137 138
     */
    private String assignedNode;

    /**
     * True if this project can be built on any node.
139
     *
140
     * <p>
141 142
     * This somewhat ugly flag combination is so that we can migrate
     * existing Hudson installations nicely.
143
     */
K
kohsuke 已提交
144
    private volatile boolean canRoam;
145 146 147 148

    /**
     * True to suspend new builds.
     */
K
kohsuke 已提交
149
    protected volatile boolean disabled;
150

151 152 153 154 155 156
    /**
     * True to keep builds of this project in queue when upstream projects are
     * building. False by default to keep from breaking existing behavior.
     */
    protected volatile boolean blockBuildWhenUpstreamBuilding = false;

157
    /**
158 159 160
     * Identifies {@link JDK} to be used.
     * Null if no explicit configuration is required.
     *
161
     * <p>
162 163 164
     * Can't store {@link JDK} directly because {@link Hudson} and {@link Project}
     * are saved independently.
     *
165 166
     * @see Hudson#getJDK(String)
     */
K
kohsuke 已提交
167
    private volatile String jdk;
168

169
    /**
M
mindless 已提交
170
     * @deprecated since 2007-01-29.
171 172
     */
    private transient boolean enableRemoteTrigger;
173

K
kohsuke 已提交
174
    private volatile BuildAuthorizationToken authToken = null;
175

176 177 178
    /**
     * List of all {@link Trigger}s for this project.
     */
179
    protected List<Trigger<?>> triggers = new Vector<Trigger<?>>();
180

181 182
    /**
     * {@link Action}s contributed from subsidiary objects associated with
183 184 185 186
     * {@link AbstractProject}, such as from triggers, builders, publishers, etc.
     *
     * We don't want to persist them separately, and these actions
     * come and go as configuration change, so it's kept separate.
187
     */
188
    protected transient /*final*/ List<Action> transientActions = new Vector<Action>();
189

K
kohsuke 已提交
190 191
    private boolean concurrentBuild;

192
    protected AbstractProject(ItemGroup parent, String name) {
193
        super(parent,name);
194

K
kohsuke 已提交
195
        if(!Hudson.getInstance().getNodes().isEmpty()) {
196
            // if a new job is configured with Hudson that already has slave nodes
197 198 199 200 201
            // make it roamable by default
            canRoam = true;
        }
    }

202
    @Override
203
    public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
204
        super.onLoad(parent, name);
205 206

        this.builds = new RunMap<R>();
207
        this.builds.load(this,new Constructor<R>() {
208 209 210 211 212
            public R create(File dir) throws IOException {
                return loadBuild(dir);
            }
        });

213
        if(triggers==null)
214
            // it didn't exist in < 1.28
215
            triggers = new Vector<Trigger<?>>();
216
        for (Trigger t : triggers)
217
            t.start(this,false);
218 219
        if(scm==null)
            scm = new NullSCM(); // perhaps it was pointing to a plugin that no longer exists.
220

221 222
        if(transientActions==null)
            transientActions = new Vector<Action>();    // happens when loaded from disk
223
        updateTransientActions();
224 225
    }

226
    @Override
227
    protected void performDelete() throws IOException, InterruptedException {
K
kohsuke 已提交
228 229
        // prevent a new build while a delete operation is in progress
        makeDisabled(true);
230
        FilePath ws = getWorkspace();
231
        if(ws!=null) {
K
NPE fix  
kohsuke 已提交
232 233 234 235
            Node on = getLastBuiltOn();
            getScm().processWorkspaceBeforeDeletion(this, ws, on);
            if(on!=null)
                on.getFileSystemProvisioner().discardWorkspace(this,ws);
236
        }
K
kohsuke 已提交
237 238 239
        super.performDelete();
    }

K
kohsuke 已提交
240 241
    /**
     * Does this project perform concurrent builds?
K
kohsuke 已提交
242
     * @since 1.319
K
kohsuke 已提交
243 244 245 246 247 248 249 250 251 252
     */
    public boolean isConcurrentBuild() {
        return Hudson.CONCURRENT_BUILD && concurrentBuild;
    }

    public void setConcurrentBuild(boolean b) throws IOException {
        concurrentBuild = b;
        save();
    }

253
    /**
254 255
     * If this project is configured to be always built on this node,
     * return that {@link Node}. Otherwise null.
256
     */
257
    public Label getAssignedLabel() {
258
        if(canRoam)
259 260
            return null;

261
        if(assignedNode==null)
262 263
            return Hudson.getInstance().getSelfLabel();
        return Hudson.getInstance().getLabel(assignedNode);
264 265
    }

K
kohsuke 已提交
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
    /**
     * Sets the assigned label.
     */
    public void setAssignedLabel(Label l) throws IOException {
        if(l==null) {
            canRoam = true;
            assignedNode = null;
        } else {
            canRoam = false;
            if(l==Hudson.getInstance().getSelfLabel())  assignedNode = null;
            else                                        assignedNode = l.getName();
        }
        save();
    }

281
    /**
282 283
     * Get the term used in the UI to represent this kind of {@link AbstractProject}.
     * Must start with a capital letter.
284 285 286
     */
    @Override
    public String getPronoun() {
287
        return Messages.AbstractProject_Pronoun();
288 289
    }

290 291 292 293 294
    /**
     * Returns the root project value.
     *
     * @return the root project value.
     */
295
    public AbstractProject getRootProject() {
296 297 298 299 300 301 302
        if (this.getParent() instanceof Hudson) {
            return this;
        } else {
            return ((AbstractProject) this.getParent()).getRootProject();
        }
    }

303 304
    /**
     * Gets the directory where the module is checked out.
305 306 307
     *
     * @return
     *      null if the workspace is on a slave that's not connected.
K
kohsuke 已提交
308
     * @deprecated as of 1.319
K
kohsuke 已提交
309 310 311 312 313
     *      To support concurrent builds of the same project, this method is moved to {@link AbstractBuild}.
     *      For backward compatibility, this method returns the right {@link AbstractBuild#getWorkspace()} if called
     *      from {@link Executor}, and otherwise the workspace of the last build.
     *
     *      <p>
314
     *      If you are calling this method during a build from an executor, switch it to {@link AbstractBuild#getWorkspace()}.
K
kohsuke 已提交
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
     *      If you are calling this method to serve a file from the workspace, doing a form validation, etc., then
     *      use {@link #getSomeWorkspace()}
     */
    public final FilePath getWorkspace() {
        Executor e = Executor.currentExecutor();
        if(e!=null) {
            Executable exe = e.getCurrentExecutable();
            if (exe instanceof AbstractBuild) {
                AbstractBuild b = (AbstractBuild) exe;
                if(b.getProject()==this)
                    return b.getWorkspace();
            }
        }
        R lb = getLastBuild();
        if(lb!=null)    return lb.getWorkspace();
        return null;
    }

    /**
     * Gets a workspace for some build of this project.
     *
     * <p>
     * This is useful for obtaining a workspace for the purpose of form field validation, where exactly
     * which build the workspace belonged is less important. The implementation makes a cursory effort
     * to find some workspace.
     *
     * @return
     *      null if there's no available workspace.
K
kohsuke 已提交
343
     * @since 1.319
344
     */
K
kohsuke 已提交
345
    public final FilePath getSomeWorkspace() {
346 347 348 349 350 351 352 353 354 355
        R b = getSomeBuildWithWorkspace();
        return b!=null ? b.getWorkspace() : null;
    }

    /**
     * Gets some build that has a live workspace.
     *
     * @return null if no such build exists.
     */
    public final R getSomeBuildWithWorkspace() {
K
kohsuke 已提交
356 357 358
        int cnt=0;
        for (R b = getLastBuild(); cnt<5 && b!=null; b=b.getPreviousBuild()) {
            FilePath ws = b.getWorkspace();
359
            if (ws!=null)   return b;
K
kohsuke 已提交
360 361 362
        }
        return null;
    }
363

364 365 366
    /**
     * Returns the root directory of the checked-out module.
     * <p>
367 368
     * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
     * and so on exists.
K
kohsuke 已提交
369
     *
K
kohsuke 已提交
370
     * @deprecated as of 1.319
K
kohsuke 已提交
371
     *      See {@link #getWorkspace()} for a migration strategy.
372 373
     */
    public FilePath getModuleRoot() {
K
kohsuke 已提交
374
        FilePath ws = getWorkspace();
375
        if(ws==null)    return null;
K
kohsuke 已提交
376
        return getScm().getModuleRoot(ws);
377 378
    }

S
stephenconnolly 已提交
379 380 381 382 383 384
    /**
     * Returns the root directories of all checked-out modules.
     * <p>
     * Some SCMs support checking out multiple modules into the same workspace.
     * In these cases, the returned array will have a length greater than one.
     * @return The roots of all modules checked out from the SCM.
K
kohsuke 已提交
385
     *
K
kohsuke 已提交
386
     * @deprecated as of 1.319
K
kohsuke 已提交
387
     *      See {@link #getWorkspace()} for a migration strategy.
S
stephenconnolly 已提交
388 389 390 391 392
     */
    public FilePath[] getModuleRoots() {
        return getScm().getModuleRoots(getWorkspace());
    }

393
    public int getQuietPeriod() {
394
        return quietPeriod!=null ? quietPeriod : Hudson.getInstance().getQuietPeriod();
395
    }
S
 
shinodkm 已提交
396
    
397 398
    public int getScmCheckoutRetryCount() {
        return scmCheckoutRetryCount !=null ? scmCheckoutRetryCount : Hudson.getInstance().getScmCheckoutRetryCount();
S
 
shinodkm 已提交
399
    }
400 401 402

    // ugly name because of EL
    public boolean getHasCustomQuietPeriod() {
403
        return quietPeriod!=null;
404
    }
S
 
shinodkm 已提交
405
    
406
    public boolean hasCustomScmCheckoutRetryCount(){
407
        return scmCheckoutRetryCount != null;
S
 
shinodkm 已提交
408
    }
409 410

    public final boolean isBuildable() {
K
kohsuke 已提交
411
        return !isDisabled();
412 413
    }

414
    /**
415 416
     * Used in <tt>sidepanel.jelly</tt> to decide whether to display
     * the config/delete/build links.
417 418 419 420 421
     */
    public boolean isConfigurable() {
        return true;
    }

422
    public boolean blockBuildWhenUpstreamBuilding() {
423
        return blockBuildWhenUpstreamBuilding;
424 425
    }

426
    public void setBlockBuildWhenUpstreamBuilding(boolean b) throws IOException {
427 428
        blockBuildWhenUpstreamBuilding = b;
        save();
429 430
    }

431 432 433
    public boolean isDisabled() {
        return disabled;
    }
S
 
shinodkm 已提交
434 435 436 437 438
    
    /**
     * Validates the retry count Regex
     */
    public FormValidation doCheckRetryCount(@QueryParameter String value)throws IOException,ServletException{
439 440 441 442 443 444 445
        // retry count is optional so this is ok
        if(value == null || value.trim().equals(""))
            return FormValidation.ok();
        if (!value.matches("[0-9]*")) {
            return FormValidation.error("Invalid retry count");
        } 
        return FormValidation.ok();
S
 
shinodkm 已提交
446
    }
447

448 449 450 451
    /**
     * Marks the build as disabled.
     */
    public void makeDisabled(boolean b) throws IOException {
452
        if(disabled==b)     return; // noop
453
        this.disabled = b;
K
bug fix  
kohsuke 已提交
454 455
        if(b)
            Hudson.getInstance().getQueue().cancel(this);
456 457 458
        save();
    }

459 460 461 462 463 464 465 466 467 468
    @CLIMethod(name="disable-job")
    public void disable() throws IOException {
        makeDisabled(true);
    }

    @CLIMethod(name="enable-job")
    public void enable() throws IOException {
        makeDisabled(false);
    }

K
kohsuke 已提交
469 470
    @Override
    public BallColor getIconColor() {
471
        if(isDisabled())
472
            return BallColor.DISABLED;
K
kohsuke 已提交
473 474 475
        else
            return super.getIconColor();
    }
476

477
    protected void updateTransientActions() {
478
        synchronized(transientActions) {
479
            transientActions.clear();
480

481
            for (JobProperty<? super P> p : properties) {
482 483
                Action a = p.getJobAction((P)this);
                if(a!=null)
484 485
                    transientActions.add(a);
            }
486 487 488

            for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all())
                transientActions.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null
489 490 491
        }
    }

492
    /**
493 494
     * Returns the live list of all {@link Publisher}s configured for this project.
     *
495
     * <p>
496 497
     * This method couldn't be called <tt>getPublishers()</tt> because existing methods
     * in sub-classes return different inconsistent types.
498
     */
499
    public abstract DescribableList<Publisher,Descriptor<Publisher>> getPublishersList();
500

K
kohsuke 已提交
501 502 503 504 505 506
    @Override
    public void addProperty(JobProperty<? super P> jobProp) throws IOException {
        super.addProperty(jobProp);
        updateTransientActions();
    }

507 508 509 510
    public List<ProminentProjectAction> getProminentActions() {
        List<Action> a = getActions();
        List<ProminentProjectAction> pa = new Vector<ProminentProjectAction>();
        for (Action action : a) {
511
            if(action instanceof ProminentProjectAction)
512 513 514 515 516
                pa.add((ProminentProjectAction) action);
        }
        return pa;
    }

517
    @Override
518
    public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
519
        super.doConfigSubmit(req,rsp);
520 521

        Set<AbstractProject> upstream = Collections.emptySet();
522 523
        if(req.getParameter("pseudoUpstreamTrigger")!=null) {
            upstream = new HashSet<AbstractProject>(Items.fromNameList(req.getParameter("upstreamProjects"),AbstractProject.class));
524 525 526 527 528 529 530 531 532
        }

        // dependency setting might have been changed by the user, so rebuild.
        Hudson.getInstance().rebuildDependencyGraph();

        // reflect the submission of the pseudo 'upstream build trriger'.
        // this needs to be done after we release the lock on 'this',
        // or otherwise we could dead-lock

533
        for (AbstractProject<?,?> p : Hudson.getInstance().getAllItems(AbstractProject.class)) {
534
            boolean isUpstream = upstream.contains(p);
535 536 537
            synchronized(p) {
                // does 'p' include us in its BuildTrigger? 
                DescribableList<Publisher,Descriptor<Publisher>> pl = p.getPublishersList();
538
                BuildTrigger trigger = pl.get(BuildTrigger.class);
539 540 541
                List<AbstractProject> newChildProjects = trigger == null ? new ArrayList<AbstractProject>():trigger.getChildProjects();
                if(isUpstream) {
                    if(!newChildProjects.contains(this))
542 543 544 545 546
                        newChildProjects.add(this);
                } else {
                    newChildProjects.remove(this);
                }

547
                if(newChildProjects.isEmpty()) {
548
                    pl.remove(BuildTrigger.class);
549
                } else {
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
                    // here, we just need to replace the old one with the new one,
                    // but there was a regression (we don't know when it started) that put multiple BuildTriggers
                    // into the list.
                    // for us not to lose the data, we need to merge them all.
                    List<BuildTrigger> existingList = pl.getAll(BuildTrigger.class);
                    BuildTrigger existing;
                    switch (existingList.size()) {
                    case 0:
                        existing = null;
                        break;
                    case 1:
                        existing = existingList.get(0);
                        break;
                    default:
                        pl.removeAll(BuildTrigger.class);
                        Set<AbstractProject> combinedChildren = new HashSet<AbstractProject>();
                        for (BuildTrigger bt : existingList)
                            combinedChildren.addAll(bt.getChildProjects());
                        existing = new BuildTrigger(new ArrayList<AbstractProject>(combinedChildren),existingList.get(0).getThreshold());
                        pl.add(existing);
                        break;
                    }

573 574
                    if(existing!=null && existing.hasSame(newChildProjects))
                        continue;   // no need to touch
575
                    pl.replace(new BuildTrigger(newChildProjects,
576
                        existing==null?Result.SUCCESS:existing.getThreshold()));
577 578 579 580 581 582 583 584 585 586 587
                }
            }
        }

        // notify the queue as the project might be now tied to different node
        Hudson.getInstance().getQueue().scheduleMaintenance();

        // this is to reflect the upstream build adjustments done above
        Hudson.getInstance().rebuildDependencyGraph();
    }

M
mdonohue 已提交
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
	/**
	 * @deprecated
	 *    Use {@link #scheduleBuild(Cause)}.  Since 1.283
	 */
    public boolean scheduleBuild() {
    	return scheduleBuild(new LegacyCodeCause());
    }
    
	/**
	 * @deprecated
	 *    Use {@link #scheduleBuild(int, Cause)}.  Since 1.283
	 */
    public boolean scheduleBuild(int quietPeriod) {
    	return scheduleBuild(quietPeriod, new LegacyCodeCause());
    }
    
604 605
    /**
     * Schedules a build of this project.
606 607 608 609 610
     *
     * @return
     *      true if the project is actually added to the queue.
     *      false if the queue contained it and therefore the add()
     *      was noop
611
     */
M
mdonohue 已提交
612 613
    public boolean scheduleBuild(Cause c) {
        return scheduleBuild(getQuietPeriod(), c);
K
kohsuke 已提交
614 615
    }

M
mdonohue 已提交
616
    public boolean scheduleBuild(int quietPeriod, Cause c) {
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
        return scheduleBuild(quietPeriod, c, new Action[0]);
    }

    /**
     * Schedules a build.
     *
     * Important: the actions should be persistable without outside references (e.g. don't store
     * references to this project). To provide parameters for a parameterized project, add a ParametersAction. If
     * no ParametersAction is provided for such a project, one will be created with the default parameter values.
     *
     * @param quietPeriod the quiet period to observer
     * @param c the cause for this build which should be recorded
     * @param actions a list of Actions that will be added to the build
     * @return whether the build was actually scheduled
     */
    public boolean scheduleBuild(int quietPeriod, Cause c, Action... actions) {
633 634 635 636 637 638 639 640
        return scheduleBuild2(quietPeriod,c,actions)!=null;
    }

    /**
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
     */
    public Future<R> scheduleBuild2(int quietPeriod, Cause c, Action... actions) {
641
        if (isDisabled())
642
            return null;
643

K
kohsuke 已提交
644
        List<Action> queueActions = new ArrayList<Action>(Arrays.asList(actions));
645 646 647 648
        if (isParameterized() && Util.filter(queueActions, ParametersAction.class).isEmpty()) {
            queueActions.add(new ParametersAction(getDefaultParametersValues()));
        }

S
sogabe 已提交
649 650 651 652
        if (c != null) {
            queueActions.add(new CauseAction(c));
        }

653 654 655 656
        WaitingItem i = Hudson.getInstance().getQueue().schedule(this, quietPeriod, queueActions);
        if(i!=null)
            return (Future)i.getFuture();
        return null;
657 658 659 660 661 662
    }

    private List<ParameterValue> getDefaultParametersValues() {
        ParametersDefinitionProperty paramDefProp = getProperty(ParametersDefinitionProperty.class);
        ArrayList<ParameterValue> defValues = new ArrayList<ParameterValue>();
        
M
mindless 已提交
663 664 665
        /*
         * This check is made ONLY if someone will call this method even if isParametrized() is false.
         */
666 667 668 669 670 671 672 673 674 675 676 677 678
        if(paramDefProp == null)
            return defValues;
        
        /* Scan for all parameter with an associated default values */
        for(ParameterDefinition paramDefinition : paramDefProp.getParameterDefinitions())
        {
           ParameterValue defaultValue  = paramDefinition.getDefaultParameterValue();
            
            if(defaultValue != null)
                defValues.add(defaultValue);           
        }
        
        return defValues;
K
kohsuke 已提交
679 680
    }

681
    /**
682 683 684 685
     * Schedules a build, and returns a {@link Future} object
     * to wait for the completion of the build.
     *
     * <p>
686
     * Production code shouldn't be using this, but for tests this is very convenient, so this isn't marked
687
     * as deprecated.
688
     */
M
mdonohue 已提交
689
    public Future<R> scheduleBuild2(int quietPeriod) {
690
        return scheduleBuild2(quietPeriod, new LegacyCodeCause());
M
mdonohue 已提交
691 692
    }
    
K
kohsuke 已提交
693
    /**
694 695
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
K
kohsuke 已提交
696
     */
M
mdonohue 已提交
697
    public Future<R> scheduleBuild2(int quietPeriod, Cause c) {
698 699 700
        return scheduleBuild2(quietPeriod, c, new Action[0]);
    }

701 702 703 704
    /**
     * Schedules a polling of this project.
     */
    public boolean schedulePolling() {
705
        if(isDisabled())    return false;
706
        SCMTrigger scmt = getTrigger(SCMTrigger.class);
707
        if(scmt==null)      return false;
708 709 710 711
        scmt.run();
        return true;
    }

712 713 714 715 716
    /**
     * Returns true if the build is in the queue.
     */
    @Override
    public boolean isInQueue() {
717
        return Hudson.getInstance().getQueue().contains(this);
718 719
    }

K
kohsuke 已提交
720 721 722 723 724
    @Override
    public Queue.Item getQueueItem() {
        return Hudson.getInstance().getQueue().getItem(this);
    }

K
kohsuke 已提交
725 726 727
    /**
     * Gets the JDK that this project is configured with, or null.
     */
728
    public JDK getJDK() {
729
        return Hudson.getInstance().getJDK(jdk);
730 731 732 733 734
    }

    /**
     * Overwrites the JDK setting.
     */
K
kohsuke 已提交
735
    public void setJDK(JDK jdk) throws IOException {
736 737 738 739
        this.jdk = jdk.getName();
        save();
    }

740 741
    public BuildAuthorizationToken getAuthToken() {
        return authToken;
742 743 744 745 746 747 748 749 750 751
    }

    public SortedMap<Integer, ? extends R> _getRuns() {
        return builds.getView();
    }

    public void removeRun(R run) {
        this.builds.remove(run);
    }

752 753 754 755 756
    /**
     * Determines Class&lt;R>.
     */
    protected abstract Class<R> getBuildClass();

H
huybrechts 已提交
757
    // keep track of the previous time we started a build
758
    private transient long lastBuildStartTime;
H
huybrechts 已提交
759
    
760 761 762
    /**
     * Creates a new build of this project for immediate execution.
     */
H
huybrechts 已提交
763 764 765 766 767 768 769 770 771 772 773
    protected synchronized R newBuild() throws IOException {
    	// make sure we don't start two builds in the same second
    	// so the build directories will be different too
    	long timeSinceLast = System.currentTimeMillis() - lastBuildStartTime;
    	if (timeSinceLast < 1000) {
    		try {
				Thread.sleep(1000 - timeSinceLast);
			} catch (InterruptedException e) {
			}
    	}
    	lastBuildStartTime = System.currentTimeMillis();
774
        try {
775
            R lastBuild = getBuildClass().getConstructor(getClass()).newInstance(this);
776 777 778 779 780 781 782
            builds.put(lastBuild);
            return lastBuild;
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
783
            throw handleInvocationTargetException(e);
784 785 786 787
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
788

789
    private IOException handleInvocationTargetException(InvocationTargetException e) {
790
        Throwable t = e.getTargetException();
791 792 793
        if(t instanceof Error)  throw (Error)t;
        if(t instanceof RuntimeException)   throw (RuntimeException)t;
        if(t instanceof IOException)    return (IOException)t;
794 795 796
        throw new Error(t);
    }

797 798 799
    /**
     * Loads an existing build record from disk.
     */
800 801
    protected R loadBuild(File dir) throws IOException {
        try {
802
            return getBuildClass().getConstructor(getClass(),File.class).newInstance(this,dir);
803 804 805 806 807
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
808
            throw handleInvocationTargetException(e);
809 810 811 812
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
813

K
kohsuke 已提交
814 815
    /**
     * {@inheritDoc}
816
     *
K
kohsuke 已提交
817 818
     * <p>
     * Note that this method returns a read-only view of {@link Action}s.
819 820
     * {@link BuildStep}s and others who want to add a project action
     * should do so by implementing {@link BuildStep#getProjectAction(AbstractProject)}.
821 822
     *
     * @see TransientProjectActionFactory
K
kohsuke 已提交
823
     */
824
    @Override
825 826 827 828
    public synchronized List<Action> getActions() {
        // add all the transient actions, too
        List<Action> actions = new Vector<Action>(super.getActions());
        actions.addAll(transientActions);
829
        // return the read only list to cause a failure on plugins who try to add an action here
K
kohsuke 已提交
830
        return Collections.unmodifiableList(actions);
831 832
    }

833 834
    /**
     * Gets the {@link Node} where this project was last built on.
835 836 837 838
     *
     * @return
     *      null if no information is available (for example,
     *      if no build was done yet.)
839 840 841 842
     */
    public Node getLastBuiltOn() {
        // where was it built on?
        AbstractBuild b = getLastBuild();
843
        if(b==null)
844 845 846 847 848
            return null;
        else
            return b.getBuiltOn();
    }

849
    /**
850
     * {@inheritDoc}
851
     *
852
     * <p>
853
     * A project must be blocked if its own previous build is in progress,
854 855
     * or if the blockBuildWhenUpstreamBuilding option is true and an upstream
     * project is building, but derived classes can also check other conditions.
856
     */
857
    public boolean isBuildBlocked() {
858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873
        return getCauseOfBlockage()!=null;
    }

    public String getWhyBlocked() {
        CauseOfBlockage cb = getCauseOfBlockage();
        return cb!=null ? cb.getShortDescription() : null;
    }

    /**
     * Blocked because the previous build is already in progress.
     */
    public static class BecauseOfBuildInProgress extends CauseOfBlockage {
        private final AbstractBuild<?,?> build;

        public BecauseOfBuildInProgress(AbstractBuild<?, ?> build) {
            this.build = build;
874 875
        }

876 877 878 879 880 881 882 883
        public String getShortDescription() {
            Executor e = build.getExecutor();
            String eta = "";
            if (e != null)
                eta = Messages.AbstractProject_ETA(e.getEstimatedRemainingTime());
            int lbn = build.getNumber();
            return Messages.AbstractProject_BuildInProgress(lbn, eta);
        }
884 885
    }

886 887 888 889 890 891 892 893 894 895 896 897 898
    /**
     * Because the upstream build is in progress, and we are configured to wait for that.
     */
    public static class BecauseOfUpstreamBuildInProgress extends CauseOfBlockage {
        public final AbstractProject<?,?> up;

        public BecauseOfUpstreamBuildInProgress(AbstractProject<?,?> up) {
            this.up = up;
        }

        public String getShortDescription() {
            return Messages.AbstractProject_UpstreamBuildInProgress(up.getName());
        }
899 900
    }

901 902 903 904 905 906 907 908 909 910
    public CauseOfBlockage getCauseOfBlockage() {
        if (isBuilding() && !isConcurrentBuild())
            return new BecauseOfBuildInProgress(getLastBuild());
        if (blockBuildWhenUpstreamBuilding()) {
            AbstractProject<?,?> bup = getBuildingUpstream();
            if (bup!=null)
                return new BecauseOfUpstreamBuildInProgress(bup);
        }
        return null;
    }
911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927

    /**
     * Returns the project if any of the upstream project (or itself) is either
     * building or is in the queue.
     * <p>
     * This means eventually there will be an automatic triggering of
     * the given project (provided that all builds went smoothly.)
     */
    protected AbstractProject getBuildingUpstream() {
    	DependencyGraph graph = Hudson.getInstance().getDependencyGraph();
        Set<AbstractProject> tups = graph.getTransitiveUpstream(this);
        tups.add(this);
        for (AbstractProject tup : tups) {
            if(tup!=this && (tup.isBuilding() || tup.isInQueue()))
                return tup;
        }
        return null;
928 929 930 931
    }

    public final long getEstimatedDuration() {
        AbstractBuild b = getLastSuccessfulBuild();
932
        if(b==null)     return -1;
933 934

        long duration = b.getDuration();
935
        if(duration==0) return -1;
936 937 938 939

        return duration;
    }

940
    public R createExecutable() throws IOException {
941
        if(isDisabled())    return null;
942
        return newBuild();
943 944
    }

945 946 947 948
    public void checkAbortPermission() {
        checkPermission(AbstractProject.ABORT);
    }

K
kohsuke 已提交
949 950 951 952
    public boolean hasAbortPermission() {
        return hasPermission(AbstractProject.ABORT);
    }

953 954
    /**
     * Gets the {@link Resource} that represents the workspace of this project.
955
     * Useful for locking and mutual exclusion control.
K
kohsuke 已提交
956
     *
K
kohsuke 已提交
957
     * @deprecated as of 1.319
K
kohsuke 已提交
958 959 960 961 962 963 964
     *      Projects no longer have a fixed workspace, ands builds will find an available workspace via
     *      {@link WorkspaceList} for each build (furthermore, that happens after a build is started.)
     *      So a {@link Resource} representation for a workspace at the project level no longer makes sense.
     *
     *      <p>
     *      If you need to lock a workspace while you do some computation, see the source code of
     *      {@link #pollSCMChanges(TaskListener)} for how to obtain a lock of a workspace through {@link WorkspaceList}.
965 966
     */
    public Resource getWorkspaceResource() {
967
        return new Resource(getFullDisplayName()+" workspace");
968 969 970 971 972 973
    }

    /**
     * List of necessary resources to perform the build of this project.
     */
    public ResourceList getResourceList() {
974
        final Set<ResourceActivity> resourceActivities = getResourceActivities();
975
        final List<ResourceList> resourceLists = new ArrayList<ResourceList>(1 + resourceActivities.size());
976 977 978 979 980 981 982 983 984 985
        for (ResourceActivity activity : resourceActivities) {
            if (activity != this && activity != null) {
                // defensive infinite recursion and null check
                resourceLists.add(activity.getResourceList());
            }
        }
        return ResourceList.union(resourceLists);
    }

    /**
986 987
     * Set of child resource activities of the build of this project (override in child projects).
     * @return The set of child resource activities of the build of this project.
988 989
     */
    protected Set<ResourceActivity> getResourceActivities() {
K
kohsuke 已提交
990
        return Collections.emptySet();
991 992
    }

993
    public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException {
994
        SCM scm = getScm();
995 996
        if(scm==null)
            return true;    // no SCM
997 998

        try {
K
kohsuke 已提交
999
            FilePath workspace = build.getWorkspace();
1000
            workspace.mkdirs();
1001
            return scm.checkout(build, launcher, workspace, listener, changelogFile);
1002
        } catch (InterruptedException e) {
K
i18n  
kohsuke 已提交
1003
            listener.getLogger().println(Messages.AbstractProject_ScmAborted());
1004
            LOGGER.log(Level.INFO,build.toString()+" aborted",e);
1005 1006 1007 1008 1009 1010
            return false;
        }
    }

    /**
     * Checks if there's any update in SCM, and returns true if any is found.
1011
     *
1012
     * <p>
1013 1014
     * The caller is responsible for coordinating the mutual exclusion between
     * a build and polling, as both touches the workspace.
1015
     */
1016
    public boolean pollSCMChanges( TaskListener listener ) {
1017
        SCM scm = getScm();
1018
        if(scm==null) {
K
i18n  
kohsuke 已提交
1019
            listener.getLogger().println(Messages.AbstractProject_NoSCM());
1020 1021
            return false;
        }
1022
        if(isDisabled()) {
K
i18n  
kohsuke 已提交
1023
            listener.getLogger().println(Messages.AbstractProject_Disabled());
1024
            return false;
1025 1026 1027
        }

        try {
K
kohsuke 已提交
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050
            if(scm.requiresWorkspaceForPolling()) {
                // lock the workspace of the last build
                FilePath ws=null;
                R lb = getLastBuild();
                if (lb!=null)   ws = lb.getWorkspace();

                if (ws==null || !ws.exists()) {
                    // workspace offline. build now, or nothing will ever be built
                    Label label = getAssignedLabel();
                    if (label != null && label.isSelfLabel()) {
                        // if the build is fixed on a node, then attempting a build will do us
                        // no good. We should just wait for the slave to come back.
                        listener.getLogger().println(Messages.AbstractProject_NoWorkspace());
                        return false;
                    }
                    if (ws == null)
                        listener.getLogger().println(Messages.AbstractProject_WorkspaceOffline());
                    else
                        listener.getLogger().println(Messages.AbstractProject_NoWorkspace());
                    listener.getLogger().println(Messages.AbstractProject_NewBuildForWorkspace());
                    return true;
                } else {
                    WorkspaceList l = lb.getBuiltOn().toComputer().getWorkspaceList();
1051 1052 1053 1054 1055 1056
                    // if doing non-concurrent build, acquite a workspace in a way that causes builds to block for this workspace.
                    // this prevents multiple workspaces of the same job --- the behavior of Hudson < 1.319.
                    //
                    // OTOH, if a concurrent build is chosen, the user is willing to create a multiple workspace,
                    // so better throughput is achieved over time (modulo the initial cost of creating that many workspaces)
                    // by having multiple workspaces
1057
                    WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
K
kohsuke 已提交
1058 1059 1060 1061
                    try {
                        LOGGER.fine("Polling SCM changes of " + getName());
                        return scm.pollChanges(this, ws.createLauncher(listener), ws, listener);
                    } finally {
1062
                        lease.release();
K
kohsuke 已提交
1063
                    }
K
kohsuke 已提交
1064
                }
K
kohsuke 已提交
1065 1066 1067 1068
            } else {
                // polling without workspace
                LOGGER.fine("Polling SCM changes of " + getName());
                return scm.pollChanges(this, null, null, listener);
K
kohsuke 已提交
1069
            }
1070
        } catch (AbortException e) {
K
i18n  
kohsuke 已提交
1071
            listener.fatalError(Messages.AbstractProject_Aborted());
1072
            LOGGER.log(Level.FINE, "Polling "+this+" aborted",e);
1073
            return false;
1074 1075 1076 1077
        } catch (IOException e) {
            e.printStackTrace(listener.fatalError(e.getMessage()));
            return false;
        } catch (InterruptedException e) {
1078
            e.printStackTrace(listener.fatalError(Messages.AbstractProject_PollingABorted()));
1079 1080 1081 1082
            return false;
        }
    }

1083 1084
    /**
     * Returns true if this user has made a commit to this project.
1085
     *
1086 1087 1088
     * @since 1.191
     */
    public boolean hasParticipant(User user) {
1089 1090
        for( R build = getLastBuild(); build!=null; build=build.getPreviousBuild())
            if(build.hasParticipant(user))
1091 1092 1093 1094
                return true;
        return false;
    }

1095 1096 1097 1098 1099 1100 1101 1102
    public SCM getScm() {
        return scm;
    }

    public void setScm(SCM scm) {
        this.scm = scm;
    }

1103 1104 1105
    /**
     * Adds a new {@link Trigger} to this {@link Project} if not active yet.
     */
1106
    public void addTrigger(Trigger<?> trigger) throws IOException {
1107
        addToList(trigger,triggers);
1108 1109
    }

1110
    public void removeTrigger(TriggerDescriptor trigger) throws IOException {
1111
        removeFromList(trigger,triggers);
1112 1113
    }

1114 1115 1116 1117
    protected final synchronized <T extends Describable<T>>
    void addToList( T item, List<T> collection ) throws IOException {
        for( int i=0; i<collection.size(); i++ ) {
            if(collection.get(i).getDescriptor()==item.getDescriptor()) {
1118
                // replace
1119
                collection.set(i,item);
1120 1121 1122 1123 1124 1125 1126 1127 1128
                save();
                return;
            }
        }
        // add
        collection.add(item);
        save();
    }

1129 1130 1131 1132
    protected final synchronized <T extends Describable<T>>
    void removeFromList(Descriptor<T> item, List<T> collection) throws IOException {
        for( int i=0; i< collection.size(); i++ ) {
            if(collection.get(i).getDescriptor()==item) {
1133 1134 1135 1136 1137 1138 1139 1140
                // found it
                collection.remove(i);
                save();
                return;
            }
        }
    }

1141 1142
    public synchronized Map<TriggerDescriptor,Trigger> getTriggers() {
        return (Map)Descriptor.toMap(triggers);
1143 1144
    }

1145
    /**
1146
     * Gets the specific trigger, or null if the propert is not configured for this job.
1147 1148 1149
     */
    public <T extends Trigger> T getTrigger(Class<T> clazz) {
        for (Trigger p : triggers) {
1150
            if(clazz.isInstance(p))
1151 1152 1153 1154 1155
                return clazz.cast(p);
        }
        return null;
    }

1156 1157 1158 1159 1160
//
//
// fingerprint related
//
//
1161 1162 1163 1164 1165 1166
    /**
     * True if the builds of this project produces {@link Fingerprint} records.
     */
    public abstract boolean isFingerprintConfigured();

    /**
1167 1168
     * Gets the other {@link AbstractProject}s that should be built
     * when a build of this project is completed.
1169
     */
K
kohsuke 已提交
1170
    @Exported
1171 1172 1173
    public final List<AbstractProject> getDownstreamProjects() {
        return Hudson.getInstance().getDependencyGraph().getDownstream(this);
    }
1174

K
kohsuke 已提交
1175
    @Exported
1176 1177
    public final List<AbstractProject> getUpstreamProjects() {
        return Hudson.getInstance().getDependencyGraph().getUpstream(this);
K
kohsuke 已提交
1178 1179
    }

K
kohsuke 已提交
1180
    /**
1181 1182 1183 1184
     * Returns only those upstream projects that defines {@link BuildTrigger} to this project.
     * This is a subset of {@link #getUpstreamProjects()}
     *
     * @return A List of upstream projects that has a {@link BuildTrigger} to this project.
K
kohsuke 已提交
1185 1186 1187
     */
    public final List<AbstractProject> getBuildTriggerUpstreamProjects() {
        ArrayList<AbstractProject> result = new ArrayList<AbstractProject>();
1188 1189
        for (AbstractProject<?,?> ap : getUpstreamProjects()) {
            BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
1190 1191 1192
            if (buildTrigger != null)
                if (buildTrigger.getChildProjects().contains(this))
                    result.add(ap);
1193
        }        
K
kohsuke 已提交
1194
        return result;
1195 1196
    }    
    
K
kohsuke 已提交
1197 1198
    /**
     * Gets all the upstream projects including transitive upstream projects.
1199
     *
K
kohsuke 已提交
1200 1201 1202
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveUpstreamProjects() {
1203
        return Hudson.getInstance().getDependencyGraph().getTransitiveUpstream(this);
K
kohsuke 已提交
1204 1205 1206
    }

    /**
1207 1208
     * Gets all the downstream projects including transitive downstream projects.
     *
K
kohsuke 已提交
1209 1210 1211
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveDownstreamProjects() {
1212
        return Hudson.getInstance().getDependencyGraph().getTransitiveDownstream(this);
1213 1214 1215 1216 1217
    }

    /**
     * Gets the dependency relationship map between this project (as the source)
     * and that project (as the sink.)
1218 1219 1220 1221
     *
     * @return
     *      can be empty but not null. build number of this project to the build
     *      numbers of that project.
1222 1223
     */
    public SortedMap<Integer, RangeSet> getRelationship(AbstractProject that) {
1224
        TreeMap<Integer,RangeSet> r = new TreeMap<Integer,RangeSet>(REVERSE_INTEGER_COMPARATOR);
1225 1226 1227 1228 1229 1230 1231 1232 1233

        checkAndRecord(that, r, this.getBuilds());
        // checkAndRecord(that, r, that.getBuilds());

        return r;
    }

    /**
     * Helper method for getDownstreamRelationship.
1234 1235
     *
     * For each given build, find the build number range of the given project and put that into the map.
1236
     */
1237
    private void checkAndRecord(AbstractProject that, TreeMap<Integer, RangeSet> r, Collection<R> builds) {
1238 1239
        for (R build : builds) {
            RangeSet rs = build.getDownstreamRelationship(that);
1240
            if(rs==null || rs.isEmpty())
1241 1242 1243 1244 1245
                continue;

            int n = build.getNumber();

            RangeSet value = r.get(n);
1246 1247
            if(value==null)
                r.put(n,rs);
1248 1249 1250 1251 1252
            else
                value.add(rs);
        }
    }

1253 1254 1255 1256 1257 1258
    /**
     * Builds the dependency graph.
     * @see DependencyGraph
     */
    protected abstract void buildDependencyGraph(DependencyGraph graph);

1259
    @Override
K
kohsuke 已提交
1260 1261
    protected SearchIndexBuilder makeSearchIndex() {
        SearchIndexBuilder sib = super.makeSearchIndex();
1262
        if(isBuildable() && hasPermission(Hudson.ADMINISTER))
1263
            sib.add("build","build");
K
kohsuke 已提交
1264 1265 1266
        return sib;
    }

1267 1268
    @Override
    protected HistoryWidget createHistoryWidget() {
1269
        return new BuildHistoryWidget<R>(this,getBuilds(),HISTORY_ADAPTER);
1270
    }
1271
    
K
kohsuke 已提交
1272
    public boolean isParameterized() {
1273
        return getProperty(ParametersDefinitionProperty.class) != null;
K
kohsuke 已提交
1274
    }
1275

1276 1277 1278 1279 1280
//
//
// actions
//
//
1281 1282 1283
    /**
     * Schedules a new build command.
     */
1284
    public void doBuild( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1285
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
1286

K
kohsuke 已提交
1287 1288 1289
        // if a build is parameterized, let that take over
        ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
        if (pp != null) {
1290
            pp._doBuild(req,rsp);
K
kohsuke 已提交
1291 1292 1293
            return;
        }

1294 1295
        Cause cause;
        if (authToken != null && authToken.getToken() != null && req.getParameter("token") != null) {
1296 1297
            // Optional additional cause text when starting via token
            String causeText = req.getParameter("cause");
1298
            cause = new RemoteCause(req.getRemoteAddr(), causeText);
1299 1300 1301 1302
        } else {
            cause = new UserCause();
        }

1303
        String delay = req.getParameter("delay");
1304
        if (delay!=null) {
M
mindless 已提交
1305 1306 1307 1308 1309
            if (!isDisabled()) {
                try {
                    // TODO: more unit handling
                    if(delay.endsWith("sec"))   delay=delay.substring(0,delay.length()-3);
                    if(delay.endsWith("secs"))  delay=delay.substring(0,delay.length()-4);
1310
                    Hudson.getInstance().getQueue().schedule(this, Integer.parseInt(delay),
1311
                    		new CauseAction(cause));
M
mindless 已提交
1312 1313 1314 1315 1316
                } catch (NumberFormatException e) {
                    throw new ServletException("Invalid delay parameter value: "+delay);
                }
            }
        } else {
1317
            scheduleBuild(cause);
1318
        }
1319 1320
        rsp.forwardToPreviousPage(req);
    }
1321

1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337
    /**
     * Supports build trigger with parameters via an HTTP GET or POST.
     * Currently only String parameters are supported.
     */
    public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp) throws IOException {
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);

        ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
        if (pp != null) {
            pp.buildWithParameters(req,rsp);
            return;
        } else {
        	throw new IllegalStateException("This build is not parameterized!");
        }
    	
    }
1338 1339 1340 1341

    /**
     * Schedules a new SCM polling command.
     */
1342
    public void doPolling( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1343 1344 1345
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
        schedulePolling();
        rsp.forwardToPreviousPage(req);
1346 1347 1348 1349 1350
    }

    /**
     * Cancels a scheduled build.
     */
1351
    public void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1352
        checkPermission(BUILD);
1353

1354
        Hudson.getInstance().getQueue().cancel(this);
1355 1356 1357
        rsp.forwardToPreviousPage(req);
    }

1358
    @Override
1359 1360
    protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
        super.submit(req,rsp);
1361

1362
        makeDisabled(req.getParameter("disable")!=null);
1363 1364

        jdk = req.getParameter("jdk");
1365
        if(req.getParameter("hasCustomQuietPeriod")!=null) {
1366 1367 1368 1369
            quietPeriod = Integer.parseInt(req.getParameter("quiet_period"));
        } else {
            quietPeriod = null;
        }
1370 1371
        if(req.getParameter("hasCustomScmCheckoutRetryCount")!=null) {
            scmCheckoutRetryCount = Integer.parseInt(req.getParameter("scmCheckoutRetryCount"));
S
 
shinodkm 已提交
1372
        } else {
1373
            scmCheckoutRetryCount = null;
S
 
shinodkm 已提交
1374
        }
1375 1376
        blockBuildWhenUpstreamBuilding = req.getParameter("blockBuildWhenUpstreamBuilding")!=null;

1377
        if(req.getParameter("hasSlaveAffinity")!=null) {
1378 1379
            canRoam = false;
            assignedNode = req.getParameter("slave");
1380 1381 1382
            if(assignedNode !=null) {
                if(Hudson.getInstance().getLabel(assignedNode).isEmpty())
                    assignedNode = null;   // no such label
1383 1384 1385 1386 1387 1388
            }
        } else {
            canRoam = true;
            assignedNode = null;
        }

1389
        concurrentBuild = req.getSubmittedForm().has("concurrentBuild");
K
kohsuke 已提交
1390

1391
        authToken = BuildAuthorizationToken.create(req);
1392

K
kohsuke 已提交
1393
        setScm(SCMS.parseSCM(req,this));
1394 1395 1396

        for (Trigger t : triggers)
            t.stop();
1397
        triggers = buildDescribable(req, Trigger.for_(this));
1398
        for (Trigger t : triggers)
1399
            t.start(this,true);
1400 1401

        updateTransientActions();
1402 1403
    }

K
kohsuke 已提交
1404 1405 1406 1407 1408 1409 1410 1411 1412
    /**
     * @deprecated
     *      As of 1.261. Use {@link #buildDescribable(StaplerRequest, List)} instead.
     */
    protected final <T extends Describable<T>> List<T> buildDescribable(StaplerRequest req, List<? extends Descriptor<T>> descriptors, String prefix) throws FormException, ServletException {
        return buildDescribable(req,descriptors);
    }

    protected final <T extends Describable<T>> List<T> buildDescribable(StaplerRequest req, List<? extends Descriptor<T>> descriptors)
1413
        throws FormException, ServletException {
1414

1415
        JSONObject data = req.getSubmittedForm();
1416
        List<T> r = new Vector<T>();
1417 1418 1419 1420
        for (Descriptor<T> d : descriptors) {
            String name = d.getJsonSafeClassName();
            if (req.getParameter(name) != null) {
                T instance = d.newInstance(req, data.getJSONObject(name));
1421
                r.add(instance);
1422 1423
            }
        }
1424
        return r;
1425 1426 1427 1428 1429
    }

    /**
     * Serves the workspace files.
     */
1430
    public DirectoryBrowserSupport doWs( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
1431
        checkPermission(AbstractProject.WORKSPACE);
K
kohsuke 已提交
1432
        FilePath ws = getSomeWorkspace();
1433
        if ((ws == null) || (!ws.exists())) {
1434
            // if there's no workspace, report a nice error message
1435 1436 1437 1438
            // Would be good if when asked for *plain*, do something else!
            // (E.g. return 404, or send empty doc.)
            // Not critical; client can just check if content type is not text/plain,
            // which also serves to detect old versions of Hudson.
1439
            req.getView(this,"noWorkspace.jelly").forward(req,rsp);
1440
            return null;
1441
        } else {
1442
            return new DirectoryBrowserSupport(this, ws, getDisplayName()+" workspace", "folder.gif", true);
1443 1444
        }
    }
1445

1446 1447 1448
    /**
     * Wipes out the workspace.
     */
1449
    public HttpResponse doDoWipeOutWorkspace() throws IOException, ServletException, InterruptedException {
1450
        checkPermission(BUILD);
1451 1452 1453 1454
        R b = getSomeBuildWithWorkspace();
        FilePath ws = b!=null ? b.getWorkspace() : null;
        if (ws!=null && getScm().processWorkspaceBeforeDeletion(this, ws, b.getBuiltOn())) {
            ws.deleteRecursive();
1455 1456 1457 1458
            return new HttpRedirect(".");
        } else {
            // If we get here, that means the SCM blocked the workspace deletion.
            return new ForwardToView(this,"wipeOutWorkspaceBlocked.jelly");
1459
        }
1460 1461
    }

1462
    public HttpResponse doDisable() throws IOException, ServletException {
1463 1464 1465
        requirePOST();
        checkPermission(CONFIGURE);
        makeDisabled(true);
1466
        return new HttpRedirect(".");
1467 1468
    }

1469
    public HttpResponse doEnable() throws IOException, ServletException {
1470
        requirePOST();
1471 1472
        checkPermission(CONFIGURE);
        makeDisabled(false);
1473
        return new HttpRedirect(".");
1474 1475
    }

K
kohsuke 已提交
1476 1477 1478
    /**
     * RSS feed for changes in this project.
     */
1479
    public void doRssChangelog(  StaplerRequest req, StaplerResponse rsp  ) throws IOException, ServletException {
K
kohsuke 已提交
1480 1481 1482 1483 1484 1485 1486 1487 1488
        class FeedItem {
            ChangeLogSet.Entry e;
            int idx;

            public FeedItem(Entry e, int idx) {
                this.e = e;
                this.idx = idx;
            }

1489
            AbstractBuild<?,?> getBuild() {
K
kohsuke 已提交
1490 1491 1492 1493 1494 1495
                return e.getParent().build;
            }
        }

        List<FeedItem> entries = new ArrayList<FeedItem>();

1496 1497 1498 1499
        for(R r=getLastBuild(); r!=null; r=r.getPreviousBuild()) {
            int idx=0;
            for( ChangeLogSet.Entry e : r.getChangeSet())
                entries.add(new FeedItem(e,idx++));
K
kohsuke 已提交
1500 1501
        }

1502 1503 1504 1505 1506 1507 1508
        RSS.forwardToRss(
            getDisplayName()+' '+getScm().getDescriptor().getDisplayName()+" changes",
            getUrl()+"changes",
            entries, new FeedAdapter<FeedItem>() {
                public String getEntryTitle(FeedItem item) {
                    return "#"+item.getBuild().number+' '+item.e.getMsg()+" ("+item.e.getAuthor()+")";
                }
K
kohsuke 已提交
1509

1510 1511 1512
                public String getEntryUrl(FeedItem item) {
                    return item.getBuild().getUrl()+"changes#detail"+item.idx;
                }
K
kohsuke 已提交
1513

1514 1515 1516
                public String getEntryID(FeedItem item) {
                    return getEntryUrl(item);
                }
K
kohsuke 已提交
1517

1518 1519 1520 1521 1522 1523
                public String getEntryDescription(FeedItem item) {
                    StringBuilder buf = new StringBuilder();
                    for(String path : item.e.getAffectedPaths())
                        buf.append(path).append('\n');
                    return buf.toString();
                }
1524

1525 1526 1527
                public Calendar getEntryTimestamp(FeedItem item) {
                    return item.getBuild().getTimestamp();
                }
1528

1529
                public String getEntryAuthor(FeedItem entry) {
1530
                    return Mailer.descriptor().getAdminAddress();
1531 1532 1533
                }
            },
            req, rsp );
K
kohsuke 已提交
1534 1535
    }

1536 1537 1538 1539 1540 1541 1542
    /**
     * {@link AbstractProject} subtypes should implement this base class as a descriptor.
     *
     * @since 1.294
     */
    public static abstract class AbstractProjectDescriptor extends TopLevelItemDescriptor {
        /**
1543
         * {@link AbstractProject} subtypes can override this method to veto some {@link Descriptor}s
1544
         * from showing up on their configuration screen. This is often useful when you are building
1545 1546
         * a workflow/company specific project type, where you want to limit the number of choices
         * given to the users.
1547 1548
         *
         * <p>
1549 1550 1551 1552
         * Some {@link Descriptor}s define their own schemes for controlling applicability
         * (such as {@link BuildStepDescriptor#isApplicable(Class)}),
         * This method works like AND in conjunction with them;
         * Both this method and that method need to return true in order for a given {@link Descriptor}
1553 1554 1555 1556
         * to show up for the given {@link Project}.
         *
         * <p>
         * The default implementation returns true for everything.
1557 1558
         *
         * @see BuildStepDescriptor#isApplicable(Class) 
K
kohsuke 已提交
1559 1560
         * @see BuildWrapperDescriptor#isApplicable(AbstractProject) 
         * @see TriggerDescriptor#isApplicable(Item)
1561
         */
K
kohsuke 已提交
1562
        @Override
1563
        public boolean isApplicable(Descriptor descriptor) {
1564 1565 1566 1567
            return true;
        }
    }

1568
    /**
1569
     * Finds a {@link AbstractProject} that has the name closest to the given name.
1570 1571
     */
    public static AbstractProject findNearest(String name) {
1572
        List<AbstractProject> projects = Hudson.getInstance().getItems(AbstractProject.class);
1573
        String[] names = new String[projects.size()];
1574
        for( int i=0; i<projects.size(); i++ )
1575 1576 1577
            names[i] = projects.get(i).getName();

        String nearest = EditDistance.findNearest(name, names);
1578
        return (AbstractProject)Hudson.getInstance().getItem(nearest);
1579
    }
1580 1581 1582

    private static final Comparator<Integer> REVERSE_INTEGER_COMPARATOR = new Comparator<Integer>() {
        public int compare(Integer o1, Integer o2) {
1583
            return o2-o1;
1584 1585
        }
    };
1586

1587
    private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName());
1588

1589
    /**
1590
     * Permission to abort a build. For now, let's make it the same as {@link #BUILD}
1591 1592
     */
    public static final Permission ABORT = BUILD;
1593
}
1594