AbstractProject.java 45.4 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.maven.MavenModule;
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;
K
kohsuke 已提交
38
import hudson.model.listeners.RunListener;
39
import hudson.remoting.AsyncFutureImpl;
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 53
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrapperDescriptor;
K
kohsuke 已提交
54
import hudson.triggers.SCMTrigger;
55
import hudson.triggers.Trigger;
56
import hudson.triggers.TriggerDescriptor;
57
import hudson.util.DescribableList;
58
import hudson.util.EditDistance;
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;
65

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

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

97
    /**
98 99 100
     * {@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()}.
101
     */
K
kohsuke 已提交
102
    private volatile SCM scm = new NullSCM();
103

104 105 106
    /**
     * All the builds keyed by their build number.
     */
107
    protected transient /*almost final*/ RunMap<R> builds = new RunMap<R>();
108 109 110 111

    /**
     * The quiet period. Null to delegate to the system default.
     */
K
kohsuke 已提交
112
    private volatile Integer quietPeriod = null;
113 114

    /**
115 116 117 118 119 120 121
     * 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.
     *
122
     * @see #canRoam
123 124 125 126 127
     */
    private String assignedNode;

    /**
     * True if this project can be built on any node.
128
     *
129
     * <p>
130 131
     * This somewhat ugly flag combination is so that we can migrate
     * existing Hudson installations nicely.
132
     */
K
kohsuke 已提交
133
    private volatile boolean canRoam;
134 135 136 137

    /**
     * True to suspend new builds.
     */
K
kohsuke 已提交
138
    protected volatile boolean disabled;
139 140

    /**
141 142 143
     * Identifies {@link JDK} to be used.
     * Null if no explicit configuration is required.
     *
144
     * <p>
145 146 147
     * Can't store {@link JDK} directly because {@link Hudson} and {@link Project}
     * are saved independently.
     *
148 149
     * @see Hudson#getJDK(String)
     */
K
kohsuke 已提交
150
    private volatile String jdk;
151

152 153 154 155
    /**
     * @deprecated
     */
    private transient boolean enableRemoteTrigger;
156

K
kohsuke 已提交
157
    private volatile BuildAuthorizationToken authToken = null;
158

159 160 161
    /**
     * List of all {@link Trigger}s for this project.
     */
162
    protected List<Trigger<?>> triggers = new Vector<Trigger<?>>();
163

164 165
    /**
     * {@link Action}s contributed from subsidiary objects associated with
166 167 168 169
     * {@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.
170
     */
171
    protected transient /*final*/ List<Action> transientActions = new Vector<Action>();
172

173
    protected AbstractProject(ItemGroup parent, String name) {
174
        super(parent,name);
175

K
kohsuke 已提交
176
        if(!Hudson.getInstance().getNodes().isEmpty()) {
177
            // if a new job is configured with Hudson that already has slave nodes
178 179 180 181 182
            // make it roamable by default
            canRoam = true;
        }
    }

183
    @Override
184
    public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
185
        super.onLoad(parent, name);
186 187

        this.builds = new RunMap<R>();
188
        this.builds.load(this,new Constructor<R>() {
189 190 191 192 193
            public R create(File dir) throws IOException {
                return loadBuild(dir);
            }
        });

194
        if(triggers==null)
195
            // it didn't exist in < 1.28
196
            triggers = new Vector<Trigger<?>>();
197
        for (Trigger t : triggers)
198
            t.start(this,false);
199

200 201
        if(transientActions==null)
            transientActions = new Vector<Action>();    // happens when loaded from disk
202
        updateTransientActions();
203 204
    }

205
    protected void performDelete() throws IOException, InterruptedException {
K
kohsuke 已提交
206 207
        // prevent a new build while a delete operation is in progress
        makeDisabled(true);
208
        FilePath ws = getWorkspace();
209
        if(ws!=null) {
K
NPE fix  
kohsuke 已提交
210 211 212 213
            Node on = getLastBuiltOn();
            getScm().processWorkspaceBeforeDeletion(this, ws, on);
            if(on!=null)
                on.getFileSystemProvisioner().discardWorkspace(this,ws);
214
        }
K
kohsuke 已提交
215 216 217
        super.performDelete();
    }

218
    /**
219 220
     * If this project is configured to be always built on this node,
     * return that {@link Node}. Otherwise null.
221
     */
222
    public Label getAssignedLabel() {
223
        if(canRoam)
224 225
            return null;

226
        if(assignedNode==null)
227 228
            return Hudson.getInstance().getSelfLabel();
        return Hudson.getInstance().getLabel(assignedNode);
229 230
    }

K
kohsuke 已提交
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
    /**
     * 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();
    }

246
    /**
247 248
     * Get the term used in the UI to represent this kind of {@link AbstractProject}.
     * Must start with a capital letter.
249 250 251
     */
    @Override
    public String getPronoun() {
252
        return Messages.AbstractProject_Pronoun();
253 254
    }

255 256 257 258 259 260 261 262 263 264 265 266 267
    /**
     * 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();
        }
    }

268 269
    /**
     * Gets the directory where the module is checked out.
270 271 272
     *
     * @return
     *      null if the workspace is on a slave that's not connected.
273
     */
274
    public abstract FilePath getWorkspace();
275

276 277 278
    /**
     * Returns the root directory of the checked-out module.
     * <p>
279 280
     * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
     * and so on exists.
281 282
     */
    public FilePath getModuleRoot() {
K
kohsuke 已提交
283
        FilePath ws = getWorkspace();
284
        if(ws==null)    return null;
K
kohsuke 已提交
285
        return getScm().getModuleRoot(ws);
286 287
    }

S
stephenconnolly 已提交
288 289 290 291 292 293 294 295 296 297 298
    /**
     * 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.
     */
    public FilePath[] getModuleRoots() {
        return getScm().getModuleRoots(getWorkspace());
    }

299
    public int getQuietPeriod() {
300
        return quietPeriod!=null ? quietPeriod : Hudson.getInstance().getQuietPeriod();
301 302 303 304
    }

    // ugly name because of EL
    public boolean getHasCustomQuietPeriod() {
305
        return quietPeriod!=null;
306 307 308
    }

    public final boolean isBuildable() {
K
kohsuke 已提交
309
        return !isDisabled();
310 311
    }

312
    /**
313 314
     * Used in <tt>sidepanel.jelly</tt> to decide whether to display
     * the config/delete/build links.
315 316 317 318 319
     */
    public boolean isConfigurable() {
        return true;
    }

320 321 322 323
    public boolean isDisabled() {
        return disabled;
    }

324 325 326 327
    /**
     * Marks the build as disabled.
     */
    public void makeDisabled(boolean b) throws IOException {
328
        if(disabled==b)     return; // noop
329
        this.disabled = b;
K
bug fix  
kohsuke 已提交
330 331
        if(b)
            Hudson.getInstance().getQueue().cancel(this);
332 333 334
        save();
    }

K
kohsuke 已提交
335 336
    @Override
    public BallColor getIconColor() {
337
        if(isDisabled())
338
            return BallColor.DISABLED;
K
kohsuke 已提交
339 340 341
        else
            return super.getIconColor();
    }
342

343
    protected void updateTransientActions() {
344
        synchronized(transientActions) {
345
            transientActions.clear();
346

347
            for (JobProperty<? super P> p : properties) {
348 349
                Action a = p.getJobAction((P)this);
                if(a!=null)
350 351 352 353 354
                    transientActions.add(a);
            }
        }
    }

355
    /**
356 357
     * Returns the live list of all {@link Publisher}s configured for this project.
     *
358
     * <p>
359 360
     * This method couldn't be called <tt>getPublishers()</tt> because existing methods
     * in sub-classes return different inconsistent types.
361
     */
362
    public abstract DescribableList<Publisher,Descriptor<Publisher>> getPublishersList();
363

K
kohsuke 已提交
364 365 366 367 368 369
    @Override
    public void addProperty(JobProperty<? super P> jobProp) throws IOException {
        super.addProperty(jobProp);
        updateTransientActions();
    }

370 371 372 373
    public List<ProminentProjectAction> getProminentActions() {
        List<Action> a = getActions();
        List<ProminentProjectAction> pa = new Vector<ProminentProjectAction>();
        for (Action action : a) {
374
            if(action instanceof ProminentProjectAction)
375 376 377 378 379
                pa.add((ProminentProjectAction) action);
        }
        return pa;
    }

380
    @Override
381 382
    public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
        super.doConfigSubmit(req,rsp);
383 384

        Set<AbstractProject> upstream = Collections.emptySet();
385 386
        if(req.getParameter("pseudoUpstreamTrigger")!=null) {
            upstream = new HashSet<AbstractProject>(Items.fromNameList(req.getParameter("upstreamProjects"),AbstractProject.class));
387 388 389 390 391 392 393 394 395
        }

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

396
        for (AbstractProject<?,?> p : Hudson.getInstance().getAllItems(AbstractProject.class)) {
397
            boolean isUpstream = upstream.contains(p);
398 399 400
            synchronized(p) {
                // does 'p' include us in its BuildTrigger? 
                DescribableList<Publisher,Descriptor<Publisher>> pl = p.getPublishersList();
401
                BuildTrigger trigger = pl.get(BuildTrigger.class);
402 403 404
                List<AbstractProject> newChildProjects = trigger == null ? new ArrayList<AbstractProject>():trigger.getChildProjects();
                if(isUpstream) {
                    if(!newChildProjects.contains(this))
405 406 407 408 409
                        newChildProjects.add(this);
                } else {
                    newChildProjects.remove(this);
                }

410
                if(newChildProjects.isEmpty()) {
411
                    pl.remove(BuildTrigger.class);
412
                } else {
413
                    BuildTrigger existing = pl.get(BuildTrigger.class);
414 415
                    if(existing!=null && existing.hasSame(newChildProjects))
                        continue;   // no need to touch
416
                    pl.add(new BuildTrigger(newChildProjects,
417
                        existing==null?Result.SUCCESS:existing.getThreshold()));
418 419 420 421 422 423 424 425 426 427 428
                }
            }
        }

        // 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 已提交
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
	/**
	 * @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());
    }
    
445 446
    /**
     * Schedules a build of this project.
447 448 449 450 451
     *
     * @return
     *      true if the project is actually added to the queue.
     *      false if the queue contained it and therefore the add()
     *      was noop
452
     */
M
mdonohue 已提交
453 454
    public boolean scheduleBuild(Cause c) {
        return scheduleBuild(getQuietPeriod(), c);
K
kohsuke 已提交
455 456
    }

M
mdonohue 已提交
457
    public boolean scheduleBuild(int quietPeriod, Cause c) {
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
        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) {
474 475 476
        if (isDisabled())
            return false;

477 478 479 480 481
        List<Action> queueActions = new ArrayList(Arrays.asList(actions));
        if (isParameterized() && Util.filter(queueActions, ParametersAction.class).isEmpty()) {
            queueActions.add(new ParametersAction(getDefaultParametersValues()));
        }

S
sogabe 已提交
482 483 484 485
        if (c != null) {
            queueActions.add(new CauseAction(c));
        }

486 487 488 489
        return Hudson.getInstance().getQueue().add(
                this,
                quietPeriod,
                queueActions.toArray(new Action[queueActions.size()]));
490 491 492 493 494 495
    }

    private List<ParameterValue> getDefaultParametersValues() {
        ParametersDefinitionProperty paramDefProp = getProperty(ParametersDefinitionProperty.class);
        ArrayList<ParameterValue> defValues = new ArrayList<ParameterValue>();
        
M
mindless 已提交
496 497 498
        /*
         * This check is made ONLY if someone will call this method even if isParametrized() is false.
         */
499 500 501 502 503 504 505 506 507 508 509 510 511
        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 已提交
512 513
    }

M
mdonohue 已提交
514
	/**
515 516 517 518 519 520
     * 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 已提交
521 522 523 524 525
	 */
    public Future<R> scheduleBuild2(int quietPeriod) {
    	return scheduleBuild2(quietPeriod, new LegacyCodeCause());
    }
    
K
kohsuke 已提交
526
    /**
527 528
     * Schedules a build of this project, and returns a {@link Future} object
     * to wait for the completion of the build.
K
kohsuke 已提交
529
     */
M
mdonohue 已提交
530
    public Future<R> scheduleBuild2(int quietPeriod, Cause c) {
531 532 533 534 535 536 537 538
        return scheduleBuild2(quietPeriod, c, new Action[0]);
    }

    /**
     * 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) {
K
kohsuke 已提交
539 540
        R lastBuild = getLastBuild();
        final int n;
541 542
        if(lastBuild!=null) n = lastBuild.getNumber();
        else                n = -1;
K
kohsuke 已提交
543 544

        Future<R> f = new AsyncFutureImpl<R>() {
545
            final RunListener r = new RunListener<AbstractBuild>(AbstractBuild.class) {
546
                public void onFinalized(AbstractBuild r) {
547 548
                    if(r.getProject()==AbstractProject.this && r.getNumber()>n) {
                        set((R)r);
K
kohsuke 已提交
549 550 551 552 553
                        unregister();
                    }
                }
            };

554
            { r.register(); }
K
kohsuke 已提交
555 556
        };

557
        scheduleBuild(quietPeriod, c, actions);
K
kohsuke 已提交
558 559

        return f;
560 561
    }

562 563 564 565
    /**
     * Schedules a polling of this project.
     */
    public boolean schedulePolling() {
566
        if(isDisabled())    return false;
567
        SCMTrigger scmt = getTrigger(SCMTrigger.class);
568
        if(scmt==null)      return false;
569 570 571 572
        scmt.run();
        return true;
    }

573 574 575 576 577
    /**
     * Returns true if the build is in the queue.
     */
    @Override
    public boolean isInQueue() {
578
        return Hudson.getInstance().getQueue().contains(this);
579 580
    }

K
kohsuke 已提交
581 582 583 584 585
    @Override
    public Queue.Item getQueueItem() {
        return Hudson.getInstance().getQueue().getItem(this);
    }

586 587 588 589 590
    /**
     * Returns true if a build of this project is in progress.
     */
    public boolean isBuilding() {
        R b = getLastBuild();
591
        return b!=null && b.isBuilding();
592 593
    }

K
kohsuke 已提交
594 595 596
    /**
     * Gets the JDK that this project is configured with, or null.
     */
597
    public JDK getJDK() {
598
        return Hudson.getInstance().getJDK(jdk);
599 600 601 602 603
    }

    /**
     * Overwrites the JDK setting.
     */
K
kohsuke 已提交
604
    public void setJDK(JDK jdk) throws IOException {
605 606 607 608
        this.jdk = jdk.getName();
        save();
    }

609 610
    public BuildAuthorizationToken getAuthToken() {
        return authToken;
611 612 613 614 615 616 617 618 619 620
    }

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

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

621 622 623 624 625
    /**
     * Determines Class&lt;R>.
     */
    protected abstract Class<R> getBuildClass();

H
huybrechts 已提交
626
    // keep track of the previous time we started a build
627
    private transient long lastBuildStartTime;
H
huybrechts 已提交
628
    
629 630 631
    /**
     * Creates a new build of this project for immediate execution.
     */
H
huybrechts 已提交
632 633 634 635 636 637 638 639 640 641 642
    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();
643
        try {
644
            R lastBuild = getBuildClass().getConstructor(getClass()).newInstance(this);
645 646 647 648 649 650 651
            builds.put(lastBuild);
            return lastBuild;
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
652
            throw handleInvocationTargetException(e);
653 654 655 656
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
657

658
    private IOException handleInvocationTargetException(InvocationTargetException e) {
659
        Throwable t = e.getTargetException();
660 661 662
        if(t instanceof Error)  throw (Error)t;
        if(t instanceof RuntimeException)   throw (RuntimeException)t;
        if(t instanceof IOException)    return (IOException)t;
663 664 665
        throw new Error(t);
    }

666 667 668
    /**
     * Loads an existing build record from disk.
     */
669 670
    protected R loadBuild(File dir) throws IOException {
        try {
671
            return getBuildClass().getConstructor(getClass(),File.class).newInstance(this,dir);
672 673 674 675 676
        } catch (InstantiationException e) {
            throw new Error(e);
        } catch (IllegalAccessException e) {
            throw new Error(e);
        } catch (InvocationTargetException e) {
677
            throw handleInvocationTargetException(e);
678 679 680 681
        } catch (NoSuchMethodException e) {
            throw new Error(e);
        }
    }
682

K
kohsuke 已提交
683 684
    /**
     * {@inheritDoc}
685
     *
K
kohsuke 已提交
686 687
     * <p>
     * Note that this method returns a read-only view of {@link Action}s.
688 689
     * {@link BuildStep}s and others who want to add a project action
     * should do so by implementing {@link BuildStep#getProjectAction(AbstractProject)}.
K
kohsuke 已提交
690
     */
691 692 693 694
    public synchronized List<Action> getActions() {
        // add all the transient actions, too
        List<Action> actions = new Vector<Action>(super.getActions());
        actions.addAll(transientActions);
695
        // return the read only list to cause a failure on plugins who try to add an action here
K
kohsuke 已提交
696
        return Collections.unmodifiableList(actions);
697 698
    }

699 700
    /**
     * Gets the {@link Node} where this project was last built on.
701 702 703 704
     *
     * @return
     *      null if no information is available (for example,
     *      if no build was done yet.)
705 706 707 708
     */
    public Node getLastBuiltOn() {
        // where was it built on?
        AbstractBuild b = getLastBuild();
709
        if(b==null)
710 711 712 713 714
            return null;
        else
            return b.getBuiltOn();
    }

715
    /**
716
     * {@inheritDoc}
717
     *
718
     * <p>
719 720
     * A project must be blocked if its own previous build is in progress,
     * but derived classes can also check other conditions.
721
     */
722
    public boolean isBuildBlocked() {
723 724 725
        return isBuilding();
    }

726 727 728
    public String getWhyBlocked() {
        AbstractBuild<?, ?> build = getLastBuild();
        Executor e = build.getExecutor();
729 730
        String eta="";
        if(e!=null)
K
i18n  
kohsuke 已提交
731
            eta = Messages.AbstractProject_ETA(e.getEstimatedRemainingTime());
732
        int lbn = build.getNumber();
733
        return Messages.AbstractProject_BuildInProgress(lbn,eta);
734 735 736 737
    }

    public final long getEstimatedDuration() {
        AbstractBuild b = getLastSuccessfulBuild();
738
        if(b==null)     return -1;
739 740

        long duration = b.getDuration();
741
        if(duration==0) return -1;
742 743 744 745

        return duration;
    }

746
    public R createExecutable() throws IOException {
747
        if(isDisabled())    return null;
748
        return newBuild();
749 750
    }

751 752 753 754
    public void checkAbortPermission() {
        checkPermission(AbstractProject.ABORT);
    }

K
kohsuke 已提交
755 756 757 758
    public boolean hasAbortPermission() {
        return hasPermission(AbstractProject.ABORT);
    }

759 760
    /**
     * Gets the {@link Resource} that represents the workspace of this project.
761
     * Useful for locking and mutual exclusion control.
762 763
     */
    public Resource getWorkspaceResource() {
764
        return new Resource(getFullDisplayName()+" workspace");
765 766 767 768 769 770
    }

    /**
     * List of necessary resources to perform the build of this project.
     */
    public ResourceList getResourceList() {
771
        final Set<ResourceActivity> resourceActivities = getResourceActivities();
772
        final List<ResourceList> resourceLists = new ArrayList<ResourceList>(1 + resourceActivities.size());
773 774 775 776 777 778 779 780 781 782 783
        for (ResourceActivity activity : resourceActivities) {
            if (activity != this && activity != null) {
                // defensive infinite recursion and null check
                resourceLists.add(activity.getResourceList());
            }
        }
        resourceLists.add(new ResourceList().w(getWorkspaceResource()));
        return ResourceList.union(resourceLists);
    }

    /**
784 785
     * 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.
786 787
     */
    protected Set<ResourceActivity> getResourceActivities() {
K
kohsuke 已提交
788
        return Collections.emptySet();
789 790
    }

791
    public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException {
792
        SCM scm = getScm();
793 794
        if(scm==null)
            return true;    // no SCM
795

796 797 798
        // Acquire lock for SCMTrigger so poll won't run while we checkout/update
        SCMTrigger scmt = getTrigger(SCMTrigger.class);
        boolean locked = false;
799
        try {
800 801 802 803 804
            if (scmt!=null) {
                scmt.getLock().lockInterruptibly();
                locked = true;
            }

805 806 807
            FilePath workspace = getWorkspace();
            workspace.mkdirs();

808
            return scm.checkout(build, launcher, workspace, listener, changelogFile);
809
        } catch (InterruptedException e) {
K
i18n  
kohsuke 已提交
810
            listener.getLogger().println(Messages.AbstractProject_ScmAborted());
811
            LOGGER.log(Level.INFO,build.toString()+" aborted",e);
812
            return false;
813 814 815
        } finally {
            if (locked)
                scmt.getLock().unlock();
816 817 818 819 820
        }
    }

    /**
     * Checks if there's any update in SCM, and returns true if any is found.
821
     *
822
     * <p>
823 824
     * The caller is responsible for coordinating the mutual exclusion between
     * a build and polling, as both touches the workspace.
825
     */
826
    public boolean pollSCMChanges( TaskListener listener ) {
827
        SCM scm = getScm();
828
        if(scm==null) {
K
i18n  
kohsuke 已提交
829
            listener.getLogger().println(Messages.AbstractProject_NoSCM());
830 831
            return false;
        }
832
        if(isDisabled()) {
K
i18n  
kohsuke 已提交
833
            listener.getLogger().println(Messages.AbstractProject_Disabled());
834
            return false;
835 836 837
        }

        try {
K
kohsuke 已提交
838
            FilePath workspace = getWorkspace();
839
            if (scm.requiresWorkspaceForPolling() && (workspace == null || !workspace.exists())) {
K
kohsuke 已提交
840
                // workspace offline. build now, or nothing will ever be built
K
kohsuke 已提交
841
                Label label = getAssignedLabel();
K
kohsuke 已提交
842
                if (label != null && label.isSelfLabel()) {
843
                    // if the build is fixed on a node, then attempting a build will do us
K
kohsuke 已提交
844
                    // no good. We should just wait for the slave to come back.
845
                    listener.getLogger().println(Messages.AbstractProject_NoWorkspace());
K
kohsuke 已提交
846 847
                    return false;
                }
K
kohsuke 已提交
848
                if (workspace == null)
849
                    listener.getLogger().println(Messages.AbstractProject_WorkspaceOffline());
K
kohsuke 已提交
850
                else
851 852
                    listener.getLogger().println(Messages.AbstractProject_NoWorkspace());
                listener.getLogger().println(Messages.AbstractProject_NewBuildForWorkspace());
K
kohsuke 已提交
853 854
                return true;
            }
K
kohsuke 已提交
855

856
            Launcher launcher = workspace != null ? workspace.createLauncher(listener) : null;
K
kohsuke 已提交
857 858
            LOGGER.fine("Polling SCM changes of " + getName());
            return scm.pollChanges(this, launcher, workspace, listener);
859
        } catch (AbortException e) {
K
i18n  
kohsuke 已提交
860
            listener.fatalError(Messages.AbstractProject_Aborted());
861
            return false;
862 863 864 865
        } catch (IOException e) {
            e.printStackTrace(listener.fatalError(e.getMessage()));
            return false;
        } catch (InterruptedException e) {
866
            e.printStackTrace(listener.fatalError(Messages.AbstractProject_PollingABorted()));
867 868 869 870
            return false;
        }
    }

871 872
    /**
     * Returns true if this user has made a commit to this project.
873
     *
874 875 876
     * @since 1.191
     */
    public boolean hasParticipant(User user) {
877 878
        for( R build = getLastBuild(); build!=null; build=build.getPreviousBuild())
            if(build.hasParticipant(user))
879 880 881 882
                return true;
        return false;
    }

883 884 885 886 887 888 889 890
    public SCM getScm() {
        return scm;
    }

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

891 892 893
    /**
     * Adds a new {@link Trigger} to this {@link Project} if not active yet.
     */
894
    public void addTrigger(Trigger<?> trigger) throws IOException {
895
        addToList(trigger,triggers);
896 897
    }

898
    public void removeTrigger(TriggerDescriptor trigger) throws IOException {
899
        removeFromList(trigger,triggers);
900 901
    }

902 903 904 905
    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()) {
906
                // replace
907
                collection.set(i,item);
908 909 910 911 912 913 914 915 916
                save();
                return;
            }
        }
        // add
        collection.add(item);
        save();
    }

917 918 919 920
    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) {
921 922 923 924 925 926 927 928
                // found it
                collection.remove(i);
                save();
                return;
            }
        }
    }

929 930
    public synchronized Map<TriggerDescriptor,Trigger> getTriggers() {
        return (Map)Descriptor.toMap(triggers);
931 932
    }

933
    /**
934
     * Gets the specific trigger, or null if the propert is not configured for this job.
935 936 937
     */
    public <T extends Trigger> T getTrigger(Class<T> clazz) {
        for (Trigger p : triggers) {
938
            if(clazz.isInstance(p))
939 940 941 942 943
                return clazz.cast(p);
        }
        return null;
    }

944 945 946 947 948
//
//
// fingerprint related
//
//
949 950 951 952 953 954
    /**
     * True if the builds of this project produces {@link Fingerprint} records.
     */
    public abstract boolean isFingerprintConfigured();

    /**
955 956
     * Gets the other {@link AbstractProject}s that should be built
     * when a build of this project is completed.
957
     */
K
kohsuke 已提交
958
    @Exported
959 960 961
    public final List<AbstractProject> getDownstreamProjects() {
        return Hudson.getInstance().getDependencyGraph().getDownstream(this);
    }
962

K
kohsuke 已提交
963
    @Exported
964 965
    public final List<AbstractProject> getUpstreamProjects() {
        return Hudson.getInstance().getDependencyGraph().getUpstream(this);
K
kohsuke 已提交
966 967
    }

K
kohsuke 已提交
968
    /**
969 970 971 972
     * 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 已提交
973 974 975
     */
    public final List<AbstractProject> getBuildTriggerUpstreamProjects() {
        ArrayList<AbstractProject> result = new ArrayList<AbstractProject>();
976 977
        for (AbstractProject<?,?> ap : getUpstreamProjects()) {
            BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
978 979 980
            if (buildTrigger != null)
                if (buildTrigger.getChildProjects().contains(this))
                    result.add(ap);
981
        }        
K
kohsuke 已提交
982
        return result;
983 984
    }    
    
K
kohsuke 已提交
985 986
    /**
     * Gets all the upstream projects including transitive upstream projects.
987
     *
K
kohsuke 已提交
988 989 990
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveUpstreamProjects() {
991
        return Hudson.getInstance().getDependencyGraph().getTransitiveUpstream(this);
K
kohsuke 已提交
992 993 994
    }

    /**
995 996
     * Gets all the downstream projects including transitive downstream projects.
     *
K
kohsuke 已提交
997 998 999
     * @since 1.138
     */
    public final Set<AbstractProject> getTransitiveDownstreamProjects() {
1000
        return Hudson.getInstance().getDependencyGraph().getTransitiveDownstream(this);
1001 1002 1003 1004 1005
    }

    /**
     * Gets the dependency relationship map between this project (as the source)
     * and that project (as the sink.)
1006 1007 1008 1009
     *
     * @return
     *      can be empty but not null. build number of this project to the build
     *      numbers of that project.
1010 1011
     */
    public SortedMap<Integer, RangeSet> getRelationship(AbstractProject that) {
1012
        TreeMap<Integer,RangeSet> r = new TreeMap<Integer,RangeSet>(REVERSE_INTEGER_COMPARATOR);
1013 1014 1015 1016 1017 1018 1019 1020 1021

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

        return r;
    }

    /**
     * Helper method for getDownstreamRelationship.
1022 1023
     *
     * For each given build, find the build number range of the given project and put that into the map.
1024
     */
1025
    private void checkAndRecord(AbstractProject that, TreeMap<Integer, RangeSet> r, Collection<R> builds) {
1026 1027
        for (R build : builds) {
            RangeSet rs = build.getDownstreamRelationship(that);
1028
            if(rs==null || rs.isEmpty())
1029 1030 1031 1032 1033
                continue;

            int n = build.getNumber();

            RangeSet value = r.get(n);
1034 1035
            if(value==null)
                r.put(n,rs);
1036 1037 1038 1039 1040
            else
                value.add(rs);
        }
    }

1041 1042 1043 1044 1045 1046
    /**
     * Builds the dependency graph.
     * @see DependencyGraph
     */
    protected abstract void buildDependencyGraph(DependencyGraph graph);

K
kohsuke 已提交
1047 1048
    protected SearchIndexBuilder makeSearchIndex() {
        SearchIndexBuilder sib = super.makeSearchIndex();
1049 1050
        if(isBuildable() && Hudson.isAdmin())
            sib.add("build","build");
K
kohsuke 已提交
1051 1052 1053
        return sib;
    }

1054 1055
    @Override
    protected HistoryWidget createHistoryWidget() {
1056
        return new BuildHistoryWidget<R>(this,getBuilds(),HISTORY_ADAPTER);
1057
    }
1058
    
K
kohsuke 已提交
1059
    public boolean isParameterized() {
1060
        return getProperty(ParametersDefinitionProperty.class) != null;
K
kohsuke 已提交
1061
    }
1062

1063 1064 1065 1066 1067
//
//
// actions
//
//
1068 1069 1070
    /**
     * Schedules a new build command.
     */
1071
    public void doBuild( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1072
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
1073

K
kohsuke 已提交
1074 1075 1076
        // if a build is parameterized, let that take over
        ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
        if (pp != null) {
1077
            pp._doBuild(req,rsp);
K
kohsuke 已提交
1078 1079 1080
            return;
        }

1081 1082
        Cause cause;
        if (authToken != null && authToken.getToken() != null && req.getParameter("token") != null) {
1083 1084
            // Optional additional cause text when starting via token
            String causeText = req.getParameter("cause");
1085
            cause = new RemoteCause(req.getRemoteAddr(), causeText);
1086 1087 1088 1089
        } else {
            cause = new UserCause();
        }

1090
        String delay = req.getParameter("delay");
1091
        if (delay!=null) {
M
mindless 已提交
1092 1093 1094 1095 1096
            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);
1097
                    Hudson.getInstance().getQueue().add(this, Integer.parseInt(delay), 
1098
                    		new CauseAction(cause));
M
mindless 已提交
1099 1100 1101 1102 1103
                } catch (NumberFormatException e) {
                    throw new ServletException("Invalid delay parameter value: "+delay);
                }
            }
        } else {
1104
            scheduleBuild(cause);
1105
        }
1106 1107
        rsp.forwardToPreviousPage(req);
    }
1108

1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124
    /**
     * 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!");
        }
    	
    }
1125 1126 1127 1128

    /**
     * Schedules a new SCM polling command.
     */
1129
    public void doPolling( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1130 1131 1132
        BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
        schedulePolling();
        rsp.forwardToPreviousPage(req);
1133 1134 1135 1136 1137
    }

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

1141
        Hudson.getInstance().getQueue().cancel(this);
1142 1143 1144
        rsp.forwardToPreviousPage(req);
    }

1145
    @Override
1146 1147
    protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
        super.submit(req,rsp);
1148

1149
        makeDisabled(req.getParameter("disable")!=null);
1150 1151

        jdk = req.getParameter("jdk");
1152
        if(req.getParameter("hasCustomQuietPeriod")!=null) {
1153 1154 1155 1156 1157
            quietPeriod = Integer.parseInt(req.getParameter("quiet_period"));
        } else {
            quietPeriod = null;
        }

1158
        if(req.getParameter("hasSlaveAffinity")!=null) {
1159 1160
            canRoam = false;
            assignedNode = req.getParameter("slave");
1161 1162 1163
            if(assignedNode !=null) {
                if(Hudson.getInstance().getLabel(assignedNode).isEmpty())
                    assignedNode = null;   // no such label
1164 1165 1166 1167 1168 1169
            }
        } else {
            canRoam = true;
            assignedNode = null;
        }

1170
        authToken = BuildAuthorizationToken.create(req);
1171

1172 1173 1174 1175
        setScm(SCMS.parseSCM(req));

        for (Trigger t : triggers)
            t.stop();
1176
        triggers = buildDescribable(req, Trigger.for_(this));
1177
        for (Trigger t : triggers)
1178
            t.start(this,true);
1179 1180

        updateTransientActions();
1181 1182
    }

K
kohsuke 已提交
1183 1184 1185 1186 1187 1188 1189 1190 1191
    /**
     * @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)
1192
        throws FormException, ServletException {
1193

1194
        JSONObject data = req.getSubmittedForm();
1195
        List<T> r = new Vector<T>();
1196 1197 1198 1199
        for (Descriptor<T> d : descriptors) {
            String name = d.getJsonSafeClassName();
            if (req.getParameter(name) != null) {
                T instance = d.newInstance(req, data.getJSONObject(name));
1200
                r.add(instance);
1201 1202
            }
        }
1203
        return r;
1204 1205 1206 1207 1208
    }

    /**
     * Serves the workspace files.
     */
1209
    public void doWs( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
1210
        checkPermission(AbstractProject.WORKSPACE);
1211
        FilePath ws = getWorkspace();
1212
        if ((ws == null) || (!ws.exists())) {
1213
            // if there's no workspace, report a nice error message
1214 1215 1216 1217
            // 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.
1218
            req.getView(this,"noWorkspace.jelly").forward(req,rsp);
1219
        } else {
1220
            new DirectoryBrowserSupport(this,getDisplayName()+" workspace").serveFile(req, rsp, ws, "folder.gif", true);
1221 1222
        }
    }
1223

1224 1225 1226
    /**
     * Wipes out the workspace.
     */
1227
    public void doDoWipeOutWorkspace(StaplerResponse rsp) throws IOException, InterruptedException {
1228
        checkPermission(BUILD);
1229 1230 1231 1232
        getWorkspace().deleteRecursive();
        rsp.sendRedirect2(".");
    }

1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245
    public void doDisable(StaplerResponse rsp) throws IOException, ServletException {
        requirePOST();
        checkPermission(CONFIGURE);
        makeDisabled(true);
        rsp.sendRedirect2(".");
    }

    public void doEnable(StaplerResponse rsp) throws IOException, ServletException {
        checkPermission(CONFIGURE);
        makeDisabled(false);
        rsp.sendRedirect2(".");
    }

K
kohsuke 已提交
1246 1247 1248
    /**
     * RSS feed for changes in this project.
     */
1249
    public void doRssChangelog(  StaplerRequest req, StaplerResponse rsp  ) throws IOException, ServletException {
K
kohsuke 已提交
1250 1251 1252 1253 1254 1255 1256 1257 1258
        class FeedItem {
            ChangeLogSet.Entry e;
            int idx;

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

1259
            AbstractBuild<?,?> getBuild() {
K
kohsuke 已提交
1260 1261 1262 1263 1264 1265
                return e.getParent().build;
            }
        }

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

1266 1267 1268 1269
        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 已提交
1270 1271
        }

1272 1273 1274 1275 1276 1277 1278
        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 已提交
1279

1280 1281 1282
                public String getEntryUrl(FeedItem item) {
                    return item.getBuild().getUrl()+"changes#detail"+item.idx;
                }
K
kohsuke 已提交
1283

1284 1285 1286
                public String getEntryID(FeedItem item) {
                    return getEntryUrl(item);
                }
K
kohsuke 已提交
1287

1288 1289 1290 1291 1292 1293
                public String getEntryDescription(FeedItem item) {
                    StringBuilder buf = new StringBuilder();
                    for(String path : item.e.getAffectedPaths())
                        buf.append(path).append('\n');
                    return buf.toString();
                }
1294

1295 1296 1297
                public Calendar getEntryTimestamp(FeedItem item) {
                    return item.getBuild().getTimestamp();
                }
1298

1299
                public String getEntryAuthor(FeedItem entry) {
1300
                    return Mailer.descriptor().getAdminAddress();
1301 1302 1303
                }
            },
            req, rsp );
K
kohsuke 已提交
1304 1305
    }

1306 1307 1308 1309 1310 1311 1312
    /**
     * {@link AbstractProject} subtypes should implement this base class as a descriptor.
     *
     * @since 1.294
     */
    public static abstract class AbstractProjectDescriptor extends TopLevelItemDescriptor {
        /**
1313
         * {@link AbstractProject} subtypes can override this method to veto some {@link Descriptor}s
1314
         * from showing up on their configuration screen. This is often useful when you are building
1315 1316
         * a workflow/company specific project type, where you want to limit the number of choices
         * given to the users.
1317 1318
         *
         * <p>
1319 1320 1321 1322
         * 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}
1323 1324 1325 1326
         * to show up for the given {@link Project}.
         *
         * <p>
         * The default implementation returns true for everything.
1327 1328
         *
         * @see BuildStepDescriptor#isApplicable(Class) 
K
kohsuke 已提交
1329 1330
         * @see BuildWrapperDescriptor#isApplicable(AbstractProject) 
         * @see TriggerDescriptor#isApplicable(Item)
1331
         */
K
kohsuke 已提交
1332
        @Override
1333
        public boolean isApplicable(Descriptor descriptor) {
1334 1335 1336 1337
            return true;
        }
    }

1338
    /**
1339
     * Finds a {@link AbstractProject} that has the name closest to the given name.
1340 1341
     */
    public static AbstractProject findNearest(String name) {
1342
        List<AbstractProject> projects = Hudson.getInstance().getItems(AbstractProject.class);
1343
        String[] names = new String[projects.size()];
1344
        for( int i=0; i<projects.size(); i++ )
1345 1346 1347
            names[i] = projects.get(i).getName();

        String nearest = EditDistance.findNearest(name, names);
1348
        return (AbstractProject)Hudson.getInstance().getItem(nearest);
1349
    }
1350 1351 1352

    private static final Comparator<Integer> REVERSE_INTEGER_COMPARATOR = new Comparator<Integer>() {
        public int compare(Integer o1, Integer o2) {
1353
            return o2-o1;
1354 1355
        }
    };
1356

1357
    private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName());
1358

1359
    /**
1360
     * Permission to abort a build. For now, let's make it the same as {@link #BUILD}
1361 1362
     */
    public static final Permission ABORT = BUILD;
1363
}
1364