AbstractProject.java 50.6 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;
K
kohsuke 已提交
31
import hudson.slaves.WorkspaceList;
M
mdonohue 已提交
32
import hudson.model.Cause.LegacyCodeCause;
33
import hudson.model.Cause.UserCause;
34
import hudson.model.Cause.RemoteCause;
35 36 37
import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.RunMap.Constructor;
38
import hudson.model.Queue.WaitingItem;
K
kohsuke 已提交
39
import hudson.model.Queue.Executable;
K
kohsuke 已提交
40
import hudson.scm.ChangeLogSet;
K
kohsuke 已提交
41
import hudson.scm.ChangeLogSet.Entry;
42
import hudson.scm.NullSCM;
43
import hudson.scm.SCM;
K
kohsuke 已提交
44
import hudson.scm.SCMS;
J
jbq 已提交
45
import hudson.search.SearchIndexBuilder;
K
kohsuke 已提交
46
import hudson.security.Permission;
47
import hudson.tasks.BuildStep;
J
jbq 已提交
48
import hudson.tasks.BuildTrigger;
49
import hudson.tasks.Mailer;
50
import hudson.tasks.Publisher;
51
import hudson.tasks.BuildStepDescriptor;
K
kohsuke 已提交
52
import hudson.tasks.BuildWrapperDescriptor;
K
kohsuke 已提交
53
import hudson.triggers.SCMTrigger;
54
import hudson.triggers.Trigger;
55
import hudson.triggers.TriggerDescriptor;
56
import hudson.util.DescribableList;
57
import hudson.util.EditDistance;
S
 
shinodkm 已提交
58
import hudson.util.FormValidation;
K
kohsuke 已提交
59 60 61 62 63 64
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 已提交
65
import org.kohsuke.stapler.QueryParameter;
66 67 68
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.ForwardToView;
S
 
shinodkm 已提交
69

70

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

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

102
    /**
103 104 105
     * {@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()}.
106
     */
K
kohsuke 已提交
107
    private volatile SCM scm = new NullSCM();
108

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

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

    /**
125 126 127 128 129 130 131
     * 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.
     *
132
     * @see #canRoam
133 134 135 136 137
     */
    private String assignedNode;

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

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

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

162 163 164 165
    /**
     * @deprecated
     */
    private transient boolean enableRemoteTrigger;
166

K
kohsuke 已提交
167
    private volatile BuildAuthorizationToken authToken = null;
168

169 170 171
    /**
     * List of all {@link Trigger}s for this project.
     */
172
    protected List<Trigger<?>> triggers = new Vector<Trigger<?>>();
173

174 175
    /**
     * {@link Action}s contributed from subsidiary objects associated with
176 177 178 179
     * {@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.
180
     */
181
    protected transient /*final*/ List<Action> transientActions = new Vector<Action>();
182

K
kohsuke 已提交
183 184
    private boolean concurrentBuild;

185
    protected AbstractProject(ItemGroup parent, String name) {
186
        super(parent,name);
187

K
kohsuke 已提交
188
        if(!Hudson.getInstance().getNodes().isEmpty()) {
189
            // if a new job is configured with Hudson that already has slave nodes
190 191 192 193 194
            // make it roamable by default
            canRoam = true;
        }
    }

195
    @Override
196
    public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
197
        super.onLoad(parent, name);
198 199

        this.builds = new RunMap<R>();
200
        this.builds.load(this,new Constructor<R>() {
201 202 203 204 205
            public R create(File dir) throws IOException {
                return loadBuild(dir);
            }
        });

206
        if(triggers==null)
207
            // it didn't exist in < 1.28
208
            triggers = new Vector<Trigger<?>>();
209
        for (Trigger t : triggers)
210
            t.start(this,false);
211 212
        if(scm==null)
            scm = new NullSCM(); // perhaps it was pointing to a plugin that no longer exists.
213

214 215
        if(transientActions==null)
            transientActions = new Vector<Action>();    // happens when loaded from disk
216
        updateTransientActions();
217 218
    }

219
    protected void performDelete() throws IOException, InterruptedException {
K
kohsuke 已提交
220 221
        // prevent a new build while a delete operation is in progress
        makeDisabled(true);
222
        FilePath ws = getWorkspace();
223
        if(ws!=null) {
K
NPE fix  
kohsuke 已提交
224 225 226 227
            Node on = getLastBuiltOn();
            getScm().processWorkspaceBeforeDeletion(this, ws, on);
            if(on!=null)
                on.getFileSystemProvisioner().discardWorkspace(this,ws);
228
        }
K
kohsuke 已提交
229 230 231
        super.performDelete();
    }

K
kohsuke 已提交
232 233 234 235 236 237 238 239 240 241 242 243 244
    /**
     * Does this project perform concurrent builds?
     * @since 1.XXX
     */
    public boolean isConcurrentBuild() {
        return Hudson.CONCURRENT_BUILD && concurrentBuild;
    }

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

245
    /**
246 247
     * If this project is configured to be always built on this node,
     * return that {@link Node}. Otherwise null.
248
     */
249
    public Label getAssignedLabel() {
250
        if(canRoam)
251 252
            return null;

253
        if(assignedNode==null)
254 255
            return Hudson.getInstance().getSelfLabel();
        return Hudson.getInstance().getLabel(assignedNode);
256 257
    }

K
kohsuke 已提交
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
    /**
     * 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();
    }

273
    /**
274 275
     * Get the term used in the UI to represent this kind of {@link AbstractProject}.
     * Must start with a capital letter.
276 277 278
     */
    @Override
    public String getPronoun() {
279
        return Messages.AbstractProject_Pronoun();
280 281
    }

282 283 284 285 286 287 288 289 290 291 292 293 294
    /**
     * Returns the root project value.
     *
     * @return the root project value.
     */
	public AbstractProject getRootProject() {
        if (this.getParent() instanceof Hudson) {
            return this;
        } else {
            return ((AbstractProject) this.getParent()).getRootProject();
        }
    }

295 296
    /**
     * Gets the directory where the module is checked out.
297 298 299
     *
     * @return
     *      null if the workspace is on a slave that's not connected.
K
kohsuke 已提交
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
     * @deprecated as of 1.XXX
     *      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>
     *      If your are calling this method during a build from an executor, swtich it to {@link AbstractBuild#getWorkspace()}.
     *      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.
     * @since 1.XXX
336
     */
K
kohsuke 已提交
337 338 339 340 341 342 343 344
    public final FilePath getSomeWorkspace() {
        int cnt=0;
        for (R b = getLastBuild(); cnt<5 && b!=null; b=b.getPreviousBuild()) {
            FilePath ws = b.getWorkspace();
            if (ws!=null)   return ws;
        }
        return null;
    }
345

346 347 348
    /**
     * Returns the root directory of the checked-out module.
     * <p>
349 350
     * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
     * and so on exists.
K
kohsuke 已提交
351 352 353
     *
     * @deprecated as of 1.XXX
     *      See {@link #getWorkspace()} for a migration strategy.
354 355
     */
    public FilePath getModuleRoot() {
K
kohsuke 已提交
356
        FilePath ws = getWorkspace();
357
        if(ws==null)    return null;
K
kohsuke 已提交
358
        return getScm().getModuleRoot(ws);
359 360
    }

S
stephenconnolly 已提交
361 362 363 364 365 366
    /**
     * 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 已提交
367 368 369
     *
     * @deprecated as of 1.XXX
     *      See {@link #getWorkspace()} for a migration strategy.
S
stephenconnolly 已提交
370 371 372 373 374
     */
    public FilePath[] getModuleRoots() {
        return getScm().getModuleRoots(getWorkspace());
    }

375
    public int getQuietPeriod() {
376
        return quietPeriod!=null ? quietPeriod : Hudson.getInstance().getQuietPeriod();
377
    }
S
 
shinodkm 已提交
378
    
379 380
    public int getScmCheckoutRetryCount() {
        return scmCheckoutRetryCount !=null ? scmCheckoutRetryCount : Hudson.getInstance().getScmCheckoutRetryCount();
S
 
shinodkm 已提交
381
    }
382 383 384

    // ugly name because of EL
    public boolean getHasCustomQuietPeriod() {
385
        return quietPeriod!=null;
386
    }
S
 
shinodkm 已提交
387
    
388 389
    public boolean hasCustomScmCheckoutRetryCount(){
    	return scmCheckoutRetryCount != null;
S
 
shinodkm 已提交
390
    }
391 392

    public final boolean isBuildable() {
K
kohsuke 已提交
393
        return !isDisabled();
394 395
    }

396
    /**
397 398
     * Used in <tt>sidepanel.jelly</tt> to decide whether to display
     * the config/delete/build links.
399 400 401 402 403
     */
    public boolean isConfigurable() {
        return true;
    }

404 405 406
    public boolean isDisabled() {
        return disabled;
    }
S
 
shinodkm 已提交
407 408 409 410 411 412 413 414 415 416 417 418 419
    
    /**
     * Validates the retry count Regex
     */
    public FormValidation doCheckRetryCount(@QueryParameter String value)throws IOException,ServletException{
    	// 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();
    }
420

421 422 423 424
    /**
     * Marks the build as disabled.
     */
    public void makeDisabled(boolean b) throws IOException {
425
        if(disabled==b)     return; // noop
426
        this.disabled = b;
K
bug fix  
kohsuke 已提交
427 428
        if(b)
            Hudson.getInstance().getQueue().cancel(this);
429 430 431
        save();
    }

K
kohsuke 已提交
432 433
    @Override
    public BallColor getIconColor() {
434
        if(isDisabled())
435
            return BallColor.DISABLED;
K
kohsuke 已提交
436 437 438
        else
            return super.getIconColor();
    }
439

440
    protected void updateTransientActions() {
441
        synchronized(transientActions) {
442
            transientActions.clear();
443

444
            for (JobProperty<? super P> p : properties) {
445 446
                Action a = p.getJobAction((P)this);
                if(a!=null)
447 448 449 450 451
                    transientActions.add(a);
            }
        }
    }

452
    /**
453 454
     * Returns the live list of all {@link Publisher}s configured for this project.
     *
455
     * <p>
456 457
     * This method couldn't be called <tt>getPublishers()</tt> because existing methods
     * in sub-classes return different inconsistent types.
458
     */
459
    public abstract DescribableList<Publisher,Descriptor<Publisher>> getPublishersList();
460

K
kohsuke 已提交
461 462 463 464 465 466
    @Override
    public void addProperty(JobProperty<? super P> jobProp) throws IOException {
        super.addProperty(jobProp);
        updateTransientActions();
    }

467 468 469 470
    public List<ProminentProjectAction> getProminentActions() {
        List<Action> a = getActions();
        List<ProminentProjectAction> pa = new Vector<ProminentProjectAction>();
        for (Action action : a) {
471
            if(action instanceof ProminentProjectAction)
472 473 474 475 476
                pa.add((ProminentProjectAction) action);
        }
        return pa;
    }

477
    @Override
478 479
    public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        super.doConfigSubmit(req,rsp);
480 481

        Set<AbstractProject> upstream = Collections.emptySet();
482 483
        if(req.getParameter("pseudoUpstreamTrigger")!=null) {
            upstream = new HashSet<AbstractProject>(Items.fromNameList(req.getParameter("upstreamProjects"),AbstractProject.class));
484 485 486 487 488 489 490 491 492
        }

        // 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

493
        for (AbstractProject<?,?> p : Hudson.getInstance().getAllItems(AbstractProject.class)) {
494
            boolean isUpstream = upstream.contains(p);
495 496 497
            synchronized(p) {
                // does 'p' include us in its BuildTrigger? 
                DescribableList<Publisher,Descriptor<Publisher>> pl = p.getPublishersList();
498
                BuildTrigger trigger = pl.get(BuildTrigger.class);
499 500 501
                List<AbstractProject> newChildProjects = trigger == null ? new ArrayList<AbstractProject>():trigger.getChildProjects();
                if(isUpstream) {
                    if(!newChildProjects.contains(this))
502 503 504 505 506
                        newChildProjects.add(this);
                } else {
                    newChildProjects.remove(this);
                }

507
                if(newChildProjects.isEmpty()) {
508
                    pl.remove(BuildTrigger.class);
509
                } else {
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
                    // 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;
                    }

533 534
                    if(existing!=null && existing.hasSame(newChildProjects))
                        continue;   // no need to touch
535
                    pl.replace(new BuildTrigger(newChildProjects,
536
                        existing==null?Result.SUCCESS:existing.getThreshold()));
537 538 539 540 541 542 543 544 545 546 547
                }
            }
        }

        // 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 已提交
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
	/**
	 * @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());
    }
    
564 565
    /**
     * Schedules a build of this project.
566 567 568 569 570
     *
     * @return
     *      true if the project is actually added to the queue.
     *      false if the queue contained it and therefore the add()
     *      was noop
571
     */
M
mdonohue 已提交
572 573
    public boolean scheduleBuild(Cause c) {
        return scheduleBuild(getQuietPeriod(), c);
K
kohsuke 已提交
574 575
    }

M
mdonohue 已提交
576
    public boolean scheduleBuild(int quietPeriod, Cause c) {
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
        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) {
593 594 595 596 597 598 599 600
        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) {
601
        if (isDisabled())
602
            return null;
603

604 605 606 607 608
        List<Action> queueActions = new ArrayList(Arrays.asList(actions));
        if (isParameterized() && Util.filter(queueActions, ParametersAction.class).isEmpty()) {
            queueActions.add(new ParametersAction(getDefaultParametersValues()));
        }

S
sogabe 已提交
609 610 611 612
        if (c != null) {
            queueActions.add(new CauseAction(c));
        }

613 614 615 616
        WaitingItem i = Hudson.getInstance().getQueue().schedule(this, quietPeriod, queueActions);
        if(i!=null)
            return (Future)i.getFuture();
        return null;
617 618 619 620 621 622
    }

    private List<ParameterValue> getDefaultParametersValues() {
        ParametersDefinitionProperty paramDefProp = getProperty(ParametersDefinitionProperty.class);
        ArrayList<ParameterValue> defValues = new ArrayList<ParameterValue>();
        
M
mindless 已提交
623 624 625
        /*
         * This check is made ONLY if someone will call this method even if isParametrized() is false.
         */
626 627 628 629 630 631 632 633 634 635 636 637 638
        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 已提交
639 640
    }

M
mdonohue 已提交
641
	/**
642 643 644 645 646 647
     * Schedules a build, and returns a {@link Future} object
     * to wait for the completion of the build.
     *
     * <p>
     * Production code shouldn't be using this, but for tests, this is very convenience, so this isn't marked
     * as deprecated.
M
mdonohue 已提交
648 649 650 651 652
	 */
    public Future<R> scheduleBuild2(int quietPeriod) {
    	return scheduleBuild2(quietPeriod, new LegacyCodeCause());
    }
    
K
kohsuke 已提交
653
    /**
654 655
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
K
kohsuke 已提交
656
     */
M
mdonohue 已提交
657
    public Future<R> scheduleBuild2(int quietPeriod, Cause c) {
658 659 660
        return scheduleBuild2(quietPeriod, c, new Action[0]);
    }

661 662 663 664
    /**
     * Schedules a polling of this project.
     */
    public boolean schedulePolling() {
665
        if(isDisabled())    return false;
666
        SCMTrigger scmt = getTrigger(SCMTrigger.class);
667
        if(scmt==null)      return false;
668 669 670 671
        scmt.run();
        return true;
    }

672 673 674 675 676
    /**
     * Returns true if the build is in the queue.
     */
    @Override
    public boolean isInQueue() {
677
        return Hudson.getInstance().getQueue().contains(this);
678 679
    }

K
kohsuke 已提交
680 681 682 683 684
    @Override
    public Queue.Item getQueueItem() {
        return Hudson.getInstance().getQueue().getItem(this);
    }

K
kohsuke 已提交
685 686 687
    /**
     * Gets the JDK that this project is configured with, or null.
     */
688
    public JDK getJDK() {
689
        return Hudson.getInstance().getJDK(jdk);
690 691 692 693 694
    }

    /**
     * Overwrites the JDK setting.
     */
K
kohsuke 已提交
695
    public void setJDK(JDK jdk) throws IOException {
696 697 698 699
        this.jdk = jdk.getName();
        save();
    }

700 701
    public BuildAuthorizationToken getAuthToken() {
        return authToken;
702 703 704 705 706 707 708 709 710 711
    }

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

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

712 713 714 715 716
    /**
     * Determines Class&lt;R>.
     */
    protected abstract Class<R> getBuildClass();

H
huybrechts 已提交
717
    // keep track of the previous time we started a build
718
    private transient long lastBuildStartTime;
H
huybrechts 已提交
719
    
720 721 722
    /**
     * Creates a new build of this project for immediate execution.
     */
H
huybrechts 已提交
723 724 725 726 727 728 729 730 731 732 733
    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();
734
        try {
735
            R lastBuild = getBuildClass().getConstructor(getClass()).newInstance(this);
736 737 738 739 740 741 742
            builds.put(lastBuild);
            return lastBuild;
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
743
            throw handleInvocationTargetException(e);
744 745 746 747
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
748

749
    private IOException handleInvocationTargetException(InvocationTargetException e) {
750
        Throwable t = e.getTargetException();
751 752 753
        if(t instanceof Error)  throw (Error)t;
        if(t instanceof RuntimeException)   throw (RuntimeException)t;
        if(t instanceof IOException)    return (IOException)t;
754 755 756
        throw new Error(t);
    }

757 758 759
    /**
     * Loads an existing build record from disk.
     */
760 761
    protected R loadBuild(File dir) throws IOException {
        try {
762
            return getBuildClass().getConstructor(getClass(),File.class).newInstance(this,dir);
763 764 765 766 767
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
768
            throw handleInvocationTargetException(e);
769 770 771 772
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
773

K
kohsuke 已提交
774 775
    /**
     * {@inheritDoc}
776
     *
K
kohsuke 已提交
777 778
     * <p>
     * Note that this method returns a read-only view of {@link Action}s.
779 780
     * {@link BuildStep}s and others who want to add a project action
     * should do so by implementing {@link BuildStep#getProjectAction(AbstractProject)}.
K
kohsuke 已提交
781
     */
782 783 784 785
    public synchronized List<Action> getActions() {
        // add all the transient actions, too
        List<Action> actions = new Vector<Action>(super.getActions());
        actions.addAll(transientActions);
786
        // return the read only list to cause a failure on plugins who try to add an action here
K
kohsuke 已提交
787
        return Collections.unmodifiableList(actions);
788 789
    }

790 791
    /**
     * Gets the {@link Node} where this project was last built on.
792 793 794 795
     *
     * @return
     *      null if no information is available (for example,
     *      if no build was done yet.)
796 797 798 799
     */
    public Node getLastBuiltOn() {
        // where was it built on?
        AbstractBuild b = getLastBuild();
800
        if(b==null)
801 802 803 804 805
            return null;
        else
            return b.getBuiltOn();
    }

806
    /**
807
     * {@inheritDoc}
808
     *
809
     * <p>
810 811
     * A project must be blocked if its own previous build is in progress,
     * but derived classes can also check other conditions.
812
     */
813
    public boolean isBuildBlocked() {
K
kohsuke 已提交
814
        return isBuilding() && !isConcurrentBuild();
815 816
    }

817 818 819
    public String getWhyBlocked() {
        AbstractBuild<?, ?> build = getLastBuild();
        Executor e = build.getExecutor();
820 821
        String eta="";
        if(e!=null)
K
i18n  
kohsuke 已提交
822
            eta = Messages.AbstractProject_ETA(e.getEstimatedRemainingTime());
823
        int lbn = build.getNumber();
824
        return Messages.AbstractProject_BuildInProgress(lbn,eta);
825 826 827 828
    }

    public final long getEstimatedDuration() {
        AbstractBuild b = getLastSuccessfulBuild();
829
        if(b==null)     return -1;
830 831

        long duration = b.getDuration();
832
        if(duration==0) return -1;
833 834 835 836

        return duration;
    }

837
    public R createExecutable() throws IOException {
838
        if(isDisabled())    return null;
839
        return newBuild();
840 841
    }

842 843 844 845
    public void checkAbortPermission() {
        checkPermission(AbstractProject.ABORT);
    }

K
kohsuke 已提交
846 847 848 849
    public boolean hasAbortPermission() {
        return hasPermission(AbstractProject.ABORT);
    }

850 851
    /**
     * Gets the {@link Resource} that represents the workspace of this project.
852
     * Useful for locking and mutual exclusion control.
K
kohsuke 已提交
853 854 855 856 857 858 859 860 861
     *
     * @deprecated as of 1.XXX
     *      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}.
862 863
     */
    public Resource getWorkspaceResource() {
864
        return new Resource(getFullDisplayName()+" workspace");
865 866 867 868 869 870
    }

    /**
     * List of necessary resources to perform the build of this project.
     */
    public ResourceList getResourceList() {
871
        final Set<ResourceActivity> resourceActivities = getResourceActivities();
872
        final List<ResourceList> resourceLists = new ArrayList<ResourceList>(1 + resourceActivities.size());
873 874 875 876 877 878 879 880 881 882
        for (ResourceActivity activity : resourceActivities) {
            if (activity != this && activity != null) {
                // defensive infinite recursion and null check
                resourceLists.add(activity.getResourceList());
            }
        }
        return ResourceList.union(resourceLists);
    }

    /**
883 884
     * 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.
885 886
     */
    protected Set<ResourceActivity> getResourceActivities() {
K
kohsuke 已提交
887
        return Collections.emptySet();
888 889
    }

890
    public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException {
891
        SCM scm = getScm();
892 893
        if(scm==null)
            return true;    // no SCM
894 895

        try {
K
kohsuke 已提交
896
            FilePath workspace = build.getWorkspace();
897
            workspace.mkdirs();
898
            return scm.checkout(build, launcher, workspace, listener, changelogFile);
899
        } catch (InterruptedException e) {
K
i18n  
kohsuke 已提交
900
            listener.getLogger().println(Messages.AbstractProject_ScmAborted());
901
            LOGGER.log(Level.INFO,build.toString()+" aborted",e);
902 903 904 905 906 907
            return false;
        }
    }

    /**
     * Checks if there's any update in SCM, and returns true if any is found.
908
     *
909
     * <p>
910 911
     * The caller is responsible for coordinating the mutual exclusion between
     * a build and polling, as both touches the workspace.
912
     */
913
    public boolean pollSCMChanges( TaskListener listener ) {
914
        SCM scm = getScm();
915
        if(scm==null) {
K
i18n  
kohsuke 已提交
916
            listener.getLogger().println(Messages.AbstractProject_NoSCM());
917 918
            return false;
        }
919
        if(isDisabled()) {
K
i18n  
kohsuke 已提交
920
            listener.getLogger().println(Messages.AbstractProject_Disabled());
921
            return false;
922 923 924
        }

        try {
K
kohsuke 已提交
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954
            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();
                    l.acquire(ws);
                    try {
                        LOGGER.fine("Polling SCM changes of " + getName());
                        return scm.pollChanges(this, ws.createLauncher(listener), ws, listener);
                    } finally {
                        l.release(ws);
                    }
K
kohsuke 已提交
955
                }
K
kohsuke 已提交
956 957 958 959
            } else {
                // polling without workspace
                LOGGER.fine("Polling SCM changes of " + getName());
                return scm.pollChanges(this, null, null, listener);
K
kohsuke 已提交
960
            }
961
        } catch (AbortException e) {
K
i18n  
kohsuke 已提交
962
            listener.fatalError(Messages.AbstractProject_Aborted());
963
            LOGGER.log(Level.FINE, "Polling "+this+" aborted",e);
964
            return false;
965 966 967 968
        } catch (IOException e) {
            e.printStackTrace(listener.fatalError(e.getMessage()));
            return false;
        } catch (InterruptedException e) {
969
            e.printStackTrace(listener.fatalError(Messages.AbstractProject_PollingABorted()));
970 971 972 973
            return false;
        }
    }

974 975
    /**
     * Returns true if this user has made a commit to this project.
976
     *
977 978 979
     * @since 1.191
     */
    public boolean hasParticipant(User user) {
980 981
        for( R build = getLastBuild(); build!=null; build=build.getPreviousBuild())
            if(build.hasParticipant(user))
982 983 984 985
                return true;
        return false;
    }

986 987 988 989 990 991 992 993
    public SCM getScm() {
        return scm;
    }

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

994 995 996
    /**
     * Adds a new {@link Trigger} to this {@link Project} if not active yet.
     */
997
    public void addTrigger(Trigger<?> trigger) throws IOException {
998
        addToList(trigger,triggers);
999 1000
    }

1001
    public void removeTrigger(TriggerDescriptor trigger) throws IOException {
1002
        removeFromList(trigger,triggers);
1003 1004
    }

1005 1006 1007 1008
    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()) {
1009
                // replace
1010
                collection.set(i,item);
1011 1012 1013 1014 1015 1016 1017 1018 1019
                save();
                return;
            }
        }
        // add
        collection.add(item);
        save();
    }

1020 1021 1022 1023
    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) {
1024 1025 1026 1027 1028 1029 1030 1031
                // found it
                collection.remove(i);
                save();
                return;
            }
        }
    }

1032 1033
    public synchronized Map<TriggerDescriptor,Trigger> getTriggers() {
        return (Map)Descriptor.toMap(triggers);
1034 1035
    }

1036
    /**
1037
     * Gets the specific trigger, or null if the propert is not configured for this job.
1038 1039 1040
     */
    public <T extends Trigger> T getTrigger(Class<T> clazz) {
        for (Trigger p : triggers) {
1041
            if(clazz.isInstance(p))
1042 1043 1044 1045 1046
                return clazz.cast(p);
        }
        return null;
    }

1047 1048 1049 1050 1051
//
//
// fingerprint related
//
//
1052 1053 1054 1055 1056 1057
    /**
     * True if the builds of this project produces {@link Fingerprint} records.
     */
    public abstract boolean isFingerprintConfigured();

    /**
1058 1059
     * Gets the other {@link AbstractProject}s that should be built
     * when a build of this project is completed.
1060
     */
K
kohsuke 已提交
1061
    @Exported
1062 1063 1064
    public final List<AbstractProject> getDownstreamProjects() {
        return Hudson.getInstance().getDependencyGraph().getDownstream(this);
    }
1065

K
kohsuke 已提交
1066
    @Exported
1067 1068
    public final List<AbstractProject> getUpstreamProjects() {
        return Hudson.getInstance().getDependencyGraph().getUpstream(this);
K
kohsuke 已提交
1069 1070
    }

K
kohsuke 已提交
1071
    /**
1072 1073 1074 1075
     * 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 已提交
1076 1077 1078
     */
    public final List<AbstractProject> getBuildTriggerUpstreamProjects() {
        ArrayList<AbstractProject> result = new ArrayList<AbstractProject>();
1079 1080
        for (AbstractProject<?,?> ap : getUpstreamProjects()) {
            BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
1081 1082 1083
            if (buildTrigger != null)
                if (buildTrigger.getChildProjects().contains(this))
                    result.add(ap);
1084
        }        
K
kohsuke 已提交
1085
        return result;
1086 1087
    }    
    
K
kohsuke 已提交
1088 1089
    /**
     * Gets all the upstream projects including transitive upstream projects.
1090
     *
K
kohsuke 已提交
1091 1092 1093
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveUpstreamProjects() {
1094
        return Hudson.getInstance().getDependencyGraph().getTransitiveUpstream(this);
K
kohsuke 已提交
1095 1096 1097
    }

    /**
1098 1099
     * Gets all the downstream projects including transitive downstream projects.
     *
K
kohsuke 已提交
1100 1101 1102
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveDownstreamProjects() {
1103
        return Hudson.getInstance().getDependencyGraph().getTransitiveDownstream(this);
1104 1105 1106 1107 1108
    }

    /**
     * Gets the dependency relationship map between this project (as the source)
     * and that project (as the sink.)
1109 1110 1111 1112
     *
     * @return
     *      can be empty but not null. build number of this project to the build
     *      numbers of that project.
1113 1114
     */
    public SortedMap<Integer, RangeSet> getRelationship(AbstractProject that) {
1115
        TreeMap<Integer,RangeSet> r = new TreeMap<Integer,RangeSet>(REVERSE_INTEGER_COMPARATOR);
1116 1117 1118 1119 1120 1121 1122 1123 1124

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

        return r;
    }

    /**
     * Helper method for getDownstreamRelationship.
1125 1126
     *
     * For each given build, find the build number range of the given project and put that into the map.
1127
     */
1128
    private void checkAndRecord(AbstractProject that, TreeMap<Integer, RangeSet> r, Collection<R> builds) {
1129 1130
        for (R build : builds) {
            RangeSet rs = build.getDownstreamRelationship(that);
1131
            if(rs==null || rs.isEmpty())
1132 1133 1134 1135 1136
                continue;

            int n = build.getNumber();

            RangeSet value = r.get(n);
1137 1138
            if(value==null)
                r.put(n,rs);
1139 1140 1141 1142 1143
            else
                value.add(rs);
        }
    }

1144 1145 1146 1147 1148 1149
    /**
     * Builds the dependency graph.
     * @see DependencyGraph
     */
    protected abstract void buildDependencyGraph(DependencyGraph graph);

K
kohsuke 已提交
1150 1151
    protected SearchIndexBuilder makeSearchIndex() {
        SearchIndexBuilder sib = super.makeSearchIndex();
1152 1153
        if(isBuildable() && Hudson.isAdmin())
            sib.add("build","build");
K
kohsuke 已提交
1154 1155 1156
        return sib;
    }

1157 1158
    @Override
    protected HistoryWidget createHistoryWidget() {
1159
        return new BuildHistoryWidget<R>(this,getBuilds(),HISTORY_ADAPTER);
1160
    }
1161
    
K
kohsuke 已提交
1162
    public boolean isParameterized() {
1163
        return getProperty(ParametersDefinitionProperty.class) != null;
K
kohsuke 已提交
1164
    }
1165

1166 1167 1168 1169 1170
//
//
// actions
//
//
1171 1172 1173
    /**
     * Schedules a new build command.
     */
1174
    public void doBuild( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1175
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
1176

K
kohsuke 已提交
1177 1178 1179
        // if a build is parameterized, let that take over
        ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
        if (pp != null) {
1180
            pp._doBuild(req,rsp);
K
kohsuke 已提交
1181 1182 1183
            return;
        }

1184 1185
        Cause cause;
        if (authToken != null && authToken.getToken() != null && req.getParameter("token") != null) {
1186 1187
            // Optional additional cause text when starting via token
            String causeText = req.getParameter("cause");
1188
            cause = new RemoteCause(req.getRemoteAddr(), causeText);
1189 1190 1191 1192
        } else {
            cause = new UserCause();
        }

1193
        String delay = req.getParameter("delay");
1194
        if (delay!=null) {
M
mindless 已提交
1195 1196 1197 1198 1199
            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);
1200
                    Hudson.getInstance().getQueue().schedule(this, Integer.parseInt(delay),
1201
                    		new CauseAction(cause));
M
mindless 已提交
1202 1203 1204 1205 1206
                } catch (NumberFormatException e) {
                    throw new ServletException("Invalid delay parameter value: "+delay);
                }
            }
        } else {
1207
            scheduleBuild(cause);
1208
        }
1209 1210
        rsp.forwardToPreviousPage(req);
    }
1211

1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227
    /**
     * 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!");
        }
    	
    }
1228 1229 1230 1231

    /**
     * Schedules a new SCM polling command.
     */
1232
    public void doPolling( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1233 1234 1235
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
        schedulePolling();
        rsp.forwardToPreviousPage(req);
1236 1237 1238 1239 1240
    }

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

1244
        Hudson.getInstance().getQueue().cancel(this);
1245 1246 1247
        rsp.forwardToPreviousPage(req);
    }

1248
    @Override
1249 1250
    protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
        super.submit(req,rsp);
1251

1252
        makeDisabled(req.getParameter("disable")!=null);
1253 1254

        jdk = req.getParameter("jdk");
1255
        if(req.getParameter("hasCustomQuietPeriod")!=null) {
1256 1257 1258 1259
            quietPeriod = Integer.parseInt(req.getParameter("quiet_period"));
        } else {
            quietPeriod = null;
        }
1260 1261
        if(req.getParameter("hasCustomScmCheckoutRetryCount")!=null) {
            scmCheckoutRetryCount = Integer.parseInt(req.getParameter("scmCheckoutRetryCount"));
S
 
shinodkm 已提交
1262
        } else {
1263
        	scmCheckoutRetryCount = null;
S
 
shinodkm 已提交
1264
        }
1265

1266
        if(req.getParameter("hasSlaveAffinity")!=null) {
1267 1268
            canRoam = false;
            assignedNode = req.getParameter("slave");
1269 1270 1271
            if(assignedNode !=null) {
                if(Hudson.getInstance().getLabel(assignedNode).isEmpty())
                    assignedNode = null;   // no such label
1272 1273 1274 1275 1276 1277
            }
        } else {
            canRoam = true;
            assignedNode = null;
        }

K
kohsuke 已提交
1278 1279
        setConcurrentBuild(req.getSubmittedForm().has("concurrentBuild"));

1280
        authToken = BuildAuthorizationToken.create(req);
1281

K
kohsuke 已提交
1282
        setScm(SCMS.parseSCM(req,this));
1283 1284 1285

        for (Trigger t : triggers)
            t.stop();
1286
        triggers = buildDescribable(req, Trigger.for_(this));
1287
        for (Trigger t : triggers)
1288
            t.start(this,true);
1289 1290

        updateTransientActions();
1291 1292
    }

K
kohsuke 已提交
1293 1294 1295 1296 1297 1298 1299 1300 1301
    /**
     * @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)
1302
        throws FormException, ServletException {
1303

1304
        JSONObject data = req.getSubmittedForm();
1305
        List<T> r = new Vector<T>();
1306 1307 1308 1309
        for (Descriptor<T> d : descriptors) {
            String name = d.getJsonSafeClassName();
            if (req.getParameter(name) != null) {
                T instance = d.newInstance(req, data.getJSONObject(name));
1310
                r.add(instance);
1311 1312
            }
        }
1313
        return r;
1314 1315 1316 1317 1318
    }

    /**
     * Serves the workspace files.
     */
1319
    public void doWs( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
1320
        checkPermission(AbstractProject.WORKSPACE);
K
kohsuke 已提交
1321
        FilePath ws = getSomeWorkspace();
1322
        if ((ws == null) || (!ws.exists())) {
1323
            // if there's no workspace, report a nice error message
1324 1325 1326 1327
            // 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.
1328
            req.getView(this,"noWorkspace.jelly").forward(req,rsp);
1329
        } else {
1330
            new DirectoryBrowserSupport(this,getDisplayName()+" workspace").serveFile(req, rsp, ws, "folder.gif", true);
1331 1332
        }
    }
1333

1334 1335 1336
    /**
     * Wipes out the workspace.
     */
1337
    public HttpResponse doDoWipeOutWorkspace() throws IOException, ServletException, InterruptedException {
1338
        checkPermission(BUILD);
1339 1340
        if (getScm().processWorkspaceBeforeDeletion(this, getWorkspace(), null)) {
            getWorkspace().deleteRecursive();
1341 1342 1343 1344
            return new HttpRedirect(".");
        } else {
            // If we get here, that means the SCM blocked the workspace deletion.
            return new ForwardToView(this,"wipeOutWorkspaceBlocked.jelly");
1345
        }
1346 1347
    }

1348
    public HttpResponse doDisable() throws IOException, ServletException {
1349 1350 1351
        requirePOST();
        checkPermission(CONFIGURE);
        makeDisabled(true);
1352
        return new HttpRedirect(".");
1353 1354
    }

1355
    public HttpResponse doEnable() throws IOException, ServletException {
1356 1357
        checkPermission(CONFIGURE);
        makeDisabled(false);
1358
        return new HttpRedirect(".");
1359 1360
    }

K
kohsuke 已提交
1361 1362 1363
    /**
     * RSS feed for changes in this project.
     */
1364
    public void doRssChangelog(  StaplerRequest req, StaplerResponse rsp  ) throws IOException, ServletException {
K
kohsuke 已提交
1365 1366 1367 1368 1369 1370 1371 1372 1373
        class FeedItem {
            ChangeLogSet.Entry e;
            int idx;

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

1374
            AbstractBuild<?,?> getBuild() {
K
kohsuke 已提交
1375 1376 1377 1378 1379 1380
                return e.getParent().build;
            }
        }

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

1381 1382 1383 1384
        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 已提交
1385 1386
        }

1387 1388 1389 1390 1391 1392 1393
        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 已提交
1394

1395 1396 1397
                public String getEntryUrl(FeedItem item) {
                    return item.getBuild().getUrl()+"changes#detail"+item.idx;
                }
K
kohsuke 已提交
1398

1399 1400 1401
                public String getEntryID(FeedItem item) {
                    return getEntryUrl(item);
                }
K
kohsuke 已提交
1402

1403 1404 1405 1406 1407 1408
                public String getEntryDescription(FeedItem item) {
                    StringBuilder buf = new StringBuilder();
                    for(String path : item.e.getAffectedPaths())
                        buf.append(path).append('\n');
                    return buf.toString();
                }
1409

1410 1411 1412
                public Calendar getEntryTimestamp(FeedItem item) {
                    return item.getBuild().getTimestamp();
                }
1413

1414
                public String getEntryAuthor(FeedItem entry) {
1415
                    return Mailer.descriptor().getAdminAddress();
1416 1417 1418
                }
            },
            req, rsp );
K
kohsuke 已提交
1419 1420
    }

1421 1422 1423 1424 1425 1426 1427
    /**
     * {@link AbstractProject} subtypes should implement this base class as a descriptor.
     *
     * @since 1.294
     */
    public static abstract class AbstractProjectDescriptor extends TopLevelItemDescriptor {
        /**
1428
         * {@link AbstractProject} subtypes can override this method to veto some {@link Descriptor}s
1429
         * from showing up on their configuration screen. This is often useful when you are building
1430 1431
         * a workflow/company specific project type, where you want to limit the number of choices
         * given to the users.
1432 1433
         *
         * <p>
1434 1435 1436 1437
         * 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}
1438 1439 1440 1441
         * to show up for the given {@link Project}.
         *
         * <p>
         * The default implementation returns true for everything.
1442 1443
         *
         * @see BuildStepDescriptor#isApplicable(Class) 
K
kohsuke 已提交
1444 1445
         * @see BuildWrapperDescriptor#isApplicable(AbstractProject) 
         * @see TriggerDescriptor#isApplicable(Item)
1446
         */
K
kohsuke 已提交
1447
        @Override
1448
        public boolean isApplicable(Descriptor descriptor) {
1449 1450 1451 1452
            return true;
        }
    }

1453
    /**
1454
     * Finds a {@link AbstractProject} that has the name closest to the given name.
1455 1456
     */
    public static AbstractProject findNearest(String name) {
1457
        List<AbstractProject> projects = Hudson.getInstance().getItems(AbstractProject.class);
1458
        String[] names = new String[projects.size()];
1459
        for( int i=0; i<projects.size(); i++ )
1460 1461 1462
            names[i] = projects.get(i).getName();

        String nearest = EditDistance.findNearest(name, names);
1463
        return (AbstractProject)Hudson.getInstance().getItem(nearest);
1464
    }
1465 1466 1467

    private static final Comparator<Integer> REVERSE_INTEGER_COMPARATOR = new Comparator<Integer>() {
        public int compare(Integer o1, Integer o2) {
1468
            return o2-o1;
1469 1470
        }
    };
1471

1472
    private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName());
1473

1474
    /**
1475
     * Permission to abort a build. For now, let's make it the same as {@link #BUILD}
1476 1477
     */
    public static final Permission ABORT = BUILD;
1478
}
1479