File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\model\AbstractProject.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5 * Brian Westrich, Erik Ramfelt, Ertan Deniz, Jean-Baptiste Quenot,
6 * Luca Domenico Milanesio, R. Tyler Ballance, Stephen Connolly, Tom Huybrechts,
7 * id:cactusman, Yahoo! Inc., Andrew Bayer, Manufacture Francaise des Pneumatiques
8 * Michelin, Romain Seguy
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a copy
11 * of this software and associated documentation files (the "Software"), to deal
12 * in the Software without restriction, including without limitation the rights
13 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 * copies of the Software, and to permit persons to whom the Software is
15 * furnished to do so, subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be included in
18 * all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 * THE SOFTWARE.
27 */
28 package hudson.model;
29
30 import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
31 import hudson.EnvVars;
32 import hudson.Functions;
33 import antlr.ANTLRException;
34 import hudson.AbortException;
35 import hudson.CopyOnWrite;
36 import hudson.FeedAdapter;
37 import hudson.FilePath;
38 import hudson.Launcher;
39 import hudson.Util;
40 import hudson.cli.declarative.CLIMethod;
41 import hudson.cli.declarative.CLIResolver;
42 import hudson.model.Cause.LegacyCodeCause;
43 import hudson.model.Cause.RemoteCause;
44 import hudson.model.Cause.UserIdCause;
45 import hudson.model.Descriptor.FormException;
46 import hudson.model.Fingerprint.RangeSet;
47 import hudson.model.Queue.Executable;
48 import hudson.model.Queue.Task;
49 import hudson.model.queue.QueueTaskFuture;
50 import hudson.model.queue.SubTask;
51 import hudson.model.Queue.WaitingItem;
52 import hudson.model.RunMap.Constructor;
53 import hudson.model.labels.LabelAtom;
54 import hudson.model.labels.LabelExpression;
55 import hudson.model.listeners.SCMPollListener;
56 import hudson.model.queue.CauseOfBlockage;
57 import hudson.model.queue.SubTaskContributor;
58 import hudson.scm.ChangeLogSet;
59 import hudson.scm.ChangeLogSet.Entry;
60 import hudson.scm.NullSCM;
61 import hudson.scm.PollingResult;
62 import hudson.scm.SCM;
63 import hudson.scm.SCMRevisionState;
64 import hudson.scm.SCMS;
65 import hudson.search.SearchIndexBuilder;
66 import hudson.security.Permission;
67 import hudson.slaves.WorkspaceList;
68 import hudson.tasks.BuildStep;
69 import hudson.tasks.BuildStepDescriptor;
70 import hudson.tasks.BuildTrigger;
71 import hudson.tasks.BuildWrapperDescriptor;
72 import hudson.tasks.Publisher;
73 import hudson.triggers.SCMTrigger;
74 import hudson.triggers.Trigger;
75 import hudson.triggers.TriggerDescriptor;
76 import hudson.util.AlternativeUiTextProvider;
77 import hudson.util.AlternativeUiTextProvider.Message;
78 import hudson.util.DescribableList;
79 import hudson.util.EditDistance;
80 import hudson.util.FormValidation;
81 import hudson.widgets.BuildHistoryWidget;
82 import hudson.widgets.HistoryWidget;
83 import jenkins.model.Jenkins;
84 import jenkins.model.JenkinsLocationConfiguration;
85 import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
86 import jenkins.scm.DefaultSCMCheckoutStrategyImpl;
87 import jenkins.scm.SCMCheckoutStrategy;
88 import jenkins.scm.SCMCheckoutStrategyDescriptor;
89 import jenkins.util.TimeDuration;
90 import net.sf.json.JSONObject;
91 import org.kohsuke.accmod.Restricted;
92 import org.kohsuke.accmod.restrictions.NoExternalUse;
93 import org.kohsuke.args4j.Argument;
94 import org.kohsuke.args4j.CmdLineException;
95 import org.kohsuke.stapler.ForwardToView;
96 import org.kohsuke.stapler.HttpRedirect;
97 import org.kohsuke.stapler.HttpResponse;
98 import org.kohsuke.stapler.HttpResponses;
99 import org.kohsuke.stapler.QueryParameter;
100 import org.kohsuke.stapler.StaplerRequest;
101 import org.kohsuke.stapler.StaplerResponse;
102 import org.kohsuke.stapler.export.Exported;
103 import org.kohsuke.stapler.interceptor.RequirePOST;
104
105 import javax.servlet.ServletException;
106 import java.io.File;
107 import java.io.IOException;
108 import java.lang.reflect.InvocationTargetException;
109 import java.util.ArrayList;
110 import java.util.Arrays;
111 import java.util.Calendar;
112 import java.util.Collection;
113 import java.util.Collections;
114 import java.util.Comparator;
115 import java.util.HashSet;
116 import java.util.List;
117 import java.util.Map;
118 import java.util.Set;
119 import java.util.SortedMap;
120 import java.util.TreeMap;
121 import java.util.Vector;
122 import java.util.concurrent.Future;
123 import java.util.logging.Level;
124 import java.util.logging.Logger;
125
126 import static hudson.scm.PollingResult.*;
127 import static javax.servlet.http.HttpServletResponse.*;
128
129 /**
130 * Base implementation of {@link Job}s that build software.
131 *
132 * For now this is primarily the common part of {@link Project} and MavenModule.
133 *
134 * @author Kohsuke Kawaguchi
135 * @see AbstractBuild
136 */
137 @SuppressWarnings("rawtypes")
138 public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Job<P,R> implements BuildableItem {
139
140 /**
141 * {@link SCM} associated with the project.
142 * To allow derived classes to link {@link SCM} config to elsewhere,
143 * access to this variable should always go through {@link #getScm()}.
144 */
145 private volatile SCM scm = new NullSCM();
146
147 /**
148 * Controls how the checkout is done.
149 */
150 private volatile SCMCheckoutStrategy scmCheckoutStrategy;
151
152 /**
153 * State returned from {@link SCM#poll(AbstractProject, Launcher, FilePath, TaskListener, SCMRevisionState)}.
154 */
155 private volatile transient SCMRevisionState pollingBaseline = null;
156
157 /**
158 * All the builds keyed by their build number.
159 *
160 * External code should use {@link #getBuildByNumber(int)} or {@link #getLastBuild()} and traverse via
161 * {@link Run#getPreviousBuild()}
162 */
163 @Restricted(NoExternalUse.class)
164 @SuppressWarnings("deprecation") // [JENKINS-15156] builds accessed before onLoad or onCreatedFromScratch called
165 protected transient RunMap<R> builds = new RunMap<R>();
166
167 /**
168 * The quiet period. Null to delegate to the system default.
169 */
170 private volatile Integer quietPeriod = null;
171
172 /**
173 * The retry count. Null to delegate to the system default.
174 */
175 private volatile Integer scmCheckoutRetryCount = null;
176
177 /**
178 * If this project is configured to be only built on a certain label,
179 * this value will be set to that label.
180 *
181 * For historical reasons, this is called 'assignedNode'. Also for
182 * a historical reason, null to indicate the affinity
183 * with the master node.
184 *
185 * @see #canRoam
186 */
187 private String assignedNode;
188
189 /**
190 * True if this project can be built on any node.
191 *
192 * <p>
193 * This somewhat ugly flag combination is so that we can migrate
194 * existing Hudson installations nicely.
195 */
196 private volatile boolean canRoam;
197
198 /**
199 * True to suspend new builds.
200 */
201 protected volatile boolean disabled;
202
203 /**
204 * True to keep builds of this project in queue when downstream projects are
205 * building. False by default to keep from breaking existing behavior.
206 */
207 protected volatile boolean blockBuildWhenDownstreamBuilding = false;
208
209 /**
210 * True to keep builds of this project in queue when upstream projects are
211 * building. False by default to keep from breaking existing behavior.
212 */
213 protected volatile boolean blockBuildWhenUpstreamBuilding = false;
214
215 /**
216 * Identifies {@link JDK} to be used.
217 * Null if no explicit configuration is required.
218 *
219 * <p>
220 * Can't store {@link JDK} directly because {@link Jenkins} and {@link Project}
221 * are saved independently.
222 *
223 * @see Jenkins#getJDK(String)
224 */
225 private volatile String jdk;
226
227 private volatile BuildAuthorizationToken authToken = null;
228
229 /**
230 * List of all {@link Trigger}s for this project.
231 */
232 protected List<Trigger<?>> triggers = new Vector<Trigger<?>>();
233
234 /**
235 * {@link Action}s contributed from subsidiary objects associated with
236 * {@link AbstractProject}, such as from triggers, builders, publishers, etc.
237 *
238 * We don't want to persist them separately, and these actions
239 * come and go as configuration change, so it's kept separate.
240 */
241 @CopyOnWrite
242 protected transient volatile List<Action> transientActions = new Vector<Action>();
243
244 private boolean concurrentBuild;
245
246 /**
247 * See {@link #setCustomWorkspace(String)}.
248 *
249 * @since 1.410
250 */
251 private String customWorkspace;
252
253 protected AbstractProject(ItemGroup parent, String name) {
254 super(parent,name);
255
256 if(!Jenkins.getInstance().getNodes().isEmpty()) {
257 // if a new job is configured with Hudson that already has slave nodes
258 // make it roamable by default
259 canRoam = true;
260 }
261 }
262
263 @Override
264 public synchronized void save() throws IOException {
265 super.save();
266 updateTransientActions();
267 }
268
269 @Override
270 public void onCreatedFromScratch() {
271 super.onCreatedFromScratch();
272 builds = createBuildRunMap();
273 // solicit initial contributions, especially from TransientProjectActionFactory
274 updateTransientActions();
275 }
276
277 @Override
278 public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
279 super.onLoad(parent, name);
280
281 RunMap<R> builds = createBuildRunMap();
282 if (this.builds!=null) {
283 // if we are reloading, keep all those that are still building intact
284 for (R r : this.builds.getLoadedBuilds().values()) {
285 if (r.isBuilding())
286 builds.put(r);
287 }
288 }
289 this.builds = builds;
290 for (Trigger t : triggers())
291 t.start(this, Items.updatingByXml.get());
292 if(scm==null)
293 scm = new NullSCM(); // perhaps it was pointing to a plugin that no longer exists.
294
295 if(transientActions==null)
296 transientActions = new Vector<Action>(); // happens when loaded from disk
297 updateTransientActions();
298 }
299
300 private RunMap<R> createBuildRunMap() {
301 return new RunMap<R>(getBuildDir(), new Constructor<R>() {
302 public R create(File dir) throws IOException {
303 return loadBuild(dir);
304 }
305 });
306 }
307
308 private synchronized List<Trigger<?>> triggers() {
309 if (triggers == null) {
310 triggers = new Vector<Trigger<?>>();
311 }
312 return triggers;
313 }
314
315 @Override
316 public EnvVars getEnvironment(Node node, TaskListener listener) throws IOException, InterruptedException {
317 EnvVars env = super.getEnvironment(node, listener);
318
319 JDK jdk = getJDK();
320 if (jdk != null) {
321 if (node != null) { // just in case were not in a build
322 jdk = jdk.forNode(node, listener);
323 }
324 jdk.buildEnvVars(env);
325 }
326
327 return env;
328 }
329
330 @Override
331 protected void performDelete() throws IOException, InterruptedException {
332 // prevent a new build while a delete operation is in progress
333 makeDisabled(true);
334 FilePath ws = getWorkspace();
335 if(ws!=null) {
336 Node on = getLastBuiltOn();
337 getScm().processWorkspaceBeforeDeletion(this, ws, on);
338 if(on!=null)
339 on.getFileSystemProvisioner().discardWorkspace(this,ws);
340 }
341 super.performDelete();
342 }
343
344 /**
345 * Does this project perform concurrent builds?
346 * @since 1.319
347 */
348 @Exported
349 public boolean isConcurrentBuild() {
350 return concurrentBuild;
351 }
352
353 public void setConcurrentBuild(boolean b) throws IOException {
354 concurrentBuild = b;
355 save();
356 }
357
358 /**
359 * If this project is configured to be always built on this node,
360 * return that {@link Node}. Otherwise null.
361 */
362 public Label getAssignedLabel() {
363 if(canRoam)
364 return null;
365
366 if(assignedNode==null)
367 return Jenkins.getInstance().getSelfLabel();
368 return Jenkins.getInstance().getLabel(assignedNode);
369 }
370
371 /**
372 * Set of labels relevant to this job.
373 *
374 * This method is used to determine what slaves are relevant to jobs, for example by {@link View}s.
375 * It does not affect the scheduling. This information is informational and the best-effort basis.
376 *
377 * @since 1.456
378 * @return
379 * Minimally it should contain {@link #getAssignedLabel()}. The set can contain null element
380 * to correspond to the null return value from {@link #getAssignedLabel()}.
381 */
382 public Set<Label> getRelevantLabels() {
383 return Collections.singleton(getAssignedLabel());
384 }
385
386 /**
387 * Gets the textual representation of the assigned label as it was entered by the user.
388 */
389 public String getAssignedLabelString() {
390 if (canRoam || assignedNode==null) return null;
391 try {
392 LabelExpression.parseExpression(assignedNode);
393 return assignedNode;
394 } catch (ANTLRException e) {
395 // must be old label or host name that includes whitespace or other unsafe chars
396 return LabelAtom.escape(assignedNode);
397 }
398 }
399
400 /**
401 * Sets the assigned label.
402 */
403 public void setAssignedLabel(Label l) throws IOException {
404 if(l==null) {
405 canRoam = true;
406 assignedNode = null;
407 } else {
408 canRoam = false;
409 if(l== Jenkins.getInstance().getSelfLabel()) assignedNode = null;
410 else assignedNode = l.getExpression();
411 }
412 save();
413 }
414
415 /**
416 * Assigns this job to the given node. A convenience method over {@link #setAssignedLabel(Label)}.
417 */
418 public void setAssignedNode(Node l) throws IOException {
419 setAssignedLabel(l.getSelfLabel());
420 }
421
422 /**
423 * Get the term used in the UI to represent this kind of {@link AbstractProject}.
424 * Must start with a capital letter.
425 */
426 @Override
427 public String getPronoun() {
428 return AlternativeUiTextProvider.get(PRONOUN, this,Messages.AbstractProject_Pronoun());
429 }
430
431 /**
432 * Gets the human readable display name to be rendered in the "Build Now" link.
433 *
434 * @since 1.401
435 */
436 public String getBuildNowText() {
437 return AlternativeUiTextProvider.get(BUILD_NOW_TEXT,this,Messages.AbstractProject_BuildNow());
438 }
439
440 /**
441 * Gets the nearest ancestor {@link TopLevelItem} that's also an {@link AbstractProject}.
442 *
443 * <p>
444 * Some projects (such as matrix projects, Maven projects, or promotion processes) form a tree of jobs
445 * that acts as a single unit. This method can be used to find the top most dominating job that
446 * covers such a tree.
447 *
448 * @return never null.
449 * @see AbstractBuild#getRootBuild()
450 */
451 public AbstractProject<?,?> getRootProject() {
452 if (this instanceof TopLevelItem) {
453 return this;
454 } else {
455 ItemGroup p = this.getParent();
456 if (p instanceof AbstractProject)
457 return ((AbstractProject) p).getRootProject();
458 return this;
459 }
460 }
461
462 /**
463 * Gets the directory where the module is checked out.
464 *
465 * @return
466 * null if the workspace is on a slave that's not connected.
467 * @deprecated as of 1.319
468 * To support concurrent builds of the same project, this method is moved to {@link AbstractBuild}.
469 * For backward compatibility, this method returns the right {@link AbstractBuild#getWorkspace()} if called
470 * from {@link Executor}, and otherwise the workspace of the last build.
471 *
472 * <p>
473 * If you are calling this method during a build from an executor, switch it to {@link AbstractBuild#getWorkspace()}.
474 * If you are calling this method to serve a file from the workspace, doing a form validation, etc., then
475 * use {@link #getSomeWorkspace()}
476 */
477 public final FilePath getWorkspace() {
478 AbstractBuild b = getBuildForDeprecatedMethods();
479 return b != null ? b.getWorkspace() : null;
480
481 }
482
483 /**
484 * Various deprecated methods in this class all need the 'current' build. This method returns
485 * the build suitable for that purpose.
486 *
487 * @return An AbstractBuild for deprecated methods to use.
488 */
489 private AbstractBuild getBuildForDeprecatedMethods() {
490 Executor e = Executor.currentExecutor();
491 if(e!=null) {
492 Executable exe = e.getCurrentExecutable();
493 if (exe instanceof AbstractBuild) {
494 AbstractBuild b = (AbstractBuild) exe;
495 if(b.getProject()==this)
496 return b;
497 }
498 }
499 R lb = getLastBuild();
500 if(lb!=null) return lb;
501 return null;
502 }
503
504 /**
505 * Gets a workspace for some build of this project.
506 *
507 * <p>
508 * This is useful for obtaining a workspace for the purpose of form field validation, where exactly
509 * which build the workspace belonged is less important. The implementation makes a cursory effort
510 * to find some workspace.
511 *
512 * @return
513 * null if there's no available workspace.
514 * @since 1.319
515 */
516 public final FilePath getSomeWorkspace() {
517 R b = getSomeBuildWithWorkspace();
518 if (b!=null) return b.getWorkspace();
519 for (WorkspaceBrowser browser : Jenkins.getInstance().getExtensionList(WorkspaceBrowser.class)) {
520 FilePath f = browser.getWorkspace(this);
521 if (f != null) return f;
522 }
523 return null;
524 }
525
526 /**
527 * Gets some build that has a live workspace.
528 *
529 * @return null if no such build exists.
530 */
531 public final R getSomeBuildWithWorkspace() {
532 int cnt=0;
533 for (R b = getLastBuild(); cnt<5 && b!=null; b=b.getPreviousBuild()) {
534 FilePath ws = b.getWorkspace();
535 if (ws!=null) return b;
536 }
537 return null;
538 }
539
540 /**
541 * Returns the root directory of the checked-out module.
542 * <p>
543 * This is usually where <tt>pom.xml</tt>, <tt>build.xml</tt>
544 * and so on exists.
545 *
546 * @deprecated as of 1.319
547 * See {@link #getWorkspace()} for a migration strategy.
548 */
549 public FilePath getModuleRoot() {
550 AbstractBuild b = getBuildForDeprecatedMethods();
551 return b != null ? b.getModuleRoot() : null;
552 }
553
554 /**
555 * Returns the root directories of all checked-out modules.
556 * <p>
557 * Some SCMs support checking out multiple modules into the same workspace.
558 * In these cases, the returned array will have a length greater than one.
559 * @return The roots of all modules checked out from the SCM.
560 *
561 * @deprecated as of 1.319
562 * See {@link #getWorkspace()} for a migration strategy.
563 */
564 public FilePath[] getModuleRoots() {
565 AbstractBuild b = getBuildForDeprecatedMethods();
566 return b != null ? b.getModuleRoots() : null;
567 }
568
569 public int getQuietPeriod() {
570 return quietPeriod!=null ? quietPeriod : Jenkins.getInstance().getQuietPeriod();
571 }
572
573 public SCMCheckoutStrategy getScmCheckoutStrategy() {
574 return scmCheckoutStrategy == null ? new DefaultSCMCheckoutStrategyImpl() : scmCheckoutStrategy;
575 }
576
577 public void setScmCheckoutStrategy(SCMCheckoutStrategy scmCheckoutStrategy) throws IOException {
578 this.scmCheckoutStrategy = scmCheckoutStrategy;
579 save();
580 }
581
582
583 public int getScmCheckoutRetryCount() {
584 return scmCheckoutRetryCount !=null ? scmCheckoutRetryCount : Jenkins.getInstance().getScmCheckoutRetryCount();
585 }
586
587 // ugly name because of EL
588 public boolean getHasCustomQuietPeriod() {
589 return quietPeriod!=null;
590 }
591
592 /**
593 * Sets the custom quiet period of this project, or revert to the global default if null is given.
594 */
595 public void setQuietPeriod(Integer seconds) throws IOException {
596 this.quietPeriod = seconds;
597 save();
598 }
599
600 public boolean hasCustomScmCheckoutRetryCount(){
601 return scmCheckoutRetryCount != null;
602 }
603
604 @Override
605 public boolean isBuildable() {
606 return !isDisabled() && !isHoldOffBuildUntilSave();
607 }
608
609 /**
610 * Used in <tt>sidepanel.jelly</tt> to decide whether to display
611 * the config/delete/build links.
612 */
613 public boolean isConfigurable() {
614 return true;
615 }
616
617 public boolean blockBuildWhenDownstreamBuilding() {
618 return blockBuildWhenDownstreamBuilding;
619 }
620
621 public void setBlockBuildWhenDownstreamBuilding(boolean b) throws IOException {
622 blockBuildWhenDownstreamBuilding = b;
623 save();
624 }
625
626 public boolean blockBuildWhenUpstreamBuilding() {
627 return blockBuildWhenUpstreamBuilding;
628 }
629
630 public void setBlockBuildWhenUpstreamBuilding(boolean b) throws IOException {
631 blockBuildWhenUpstreamBuilding = b;
632 save();
633 }
634
635 public boolean isDisabled() {
636 return disabled;
637 }
638
639 /**
640 * Validates the retry count Regex
641 */
642 public FormValidation doCheckRetryCount(@QueryParameter String value)throws IOException,ServletException{
643 // retry count is optional so this is ok
644 if(value == null || value.trim().equals(""))
645 return FormValidation.ok();
646 if (!value.matches("[0-9]*")) {
647 return FormValidation.error("Invalid retry count");
648 }
649 return FormValidation.ok();
650 }
651
652 /**
653 * Marks the build as disabled.
654 */
655 public void makeDisabled(boolean b) throws IOException {
656 if(disabled==b) return; // noop
657 this.disabled = b;
658 if(b)
659 Jenkins.getInstance().getQueue().cancel(this);
660 save();
661 }
662
663 /**
664 * Specifies whether this project may be disabled by the user.
665 * By default, it can be only if this is a {@link TopLevelItem};
666 * would be false for matrix configurations, etc.
667 * @return true if the GUI should allow {@link #doDisable} and the like
668 * @since 1.475
669 */
670 public boolean supportsMakeDisabled() {
671 return this instanceof TopLevelItem;
672 }
673
674 public void disable() throws IOException {
675 makeDisabled(true);
676 }
677
678 public void enable() throws IOException {
679 makeDisabled(false);
680 }
681
682 @Override
683 public BallColor getIconColor() {
684 if(isDisabled())
685 return BallColor.DISABLED;
686 else
687 return super.getIconColor();
688 }
689
690 /**
691 * effectively deprecated. Since using updateTransientActions correctly
692 * under concurrent environment requires a lock that can too easily cause deadlocks.
693 *
694 * <p>
695 * Override {@link #createTransientActions()} instead.
696 */
697 protected void updateTransientActions() {
698 transientActions = createTransientActions();
699 }
700
701 protected List<Action> createTransientActions() {
702 Vector<Action> ta = new Vector<Action>();
703
704 for (JobProperty<? super P> p : Util.fixNull(properties))
705 ta.addAll(p.getJobActions((P)this));
706
707 for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all())
708 ta.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null
709 return ta;
710 }
711
712 /**
713 * Returns the live list of all {@link Publisher}s configured for this project.
714 *
715 * <p>
716 * This method couldn't be called <tt>getPublishers()</tt> because existing methods
717 * in sub-classes return different inconsistent types.
718 */
719 public abstract DescribableList<Publisher,Descriptor<Publisher>> getPublishersList();
720
721 @Override
722 public void addProperty(JobProperty<? super P> jobProp) throws IOException {
723 super.addProperty(jobProp);
724 updateTransientActions();
725 }
726
727 public List<ProminentProjectAction> getProminentActions() {
728 List<Action> a = getActions();
729 List<ProminentProjectAction> pa = new Vector<ProminentProjectAction>();
730 for (Action action : a) {
731 if(action instanceof ProminentProjectAction)
732 pa.add((ProminentProjectAction) action);
733 }
734 return pa;
735 }
736
737 @Override
738 public void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
739 super.doConfigSubmit(req,rsp);
740
741 updateTransientActions();
742
743 Set<AbstractProject> upstream = Collections.emptySet();
744 if(req.getParameter("pseudoUpstreamTrigger")!=null) {
745 upstream = new HashSet<AbstractProject>(Items.fromNameList(getParent(),req.getParameter("upstreamProjects"),AbstractProject.class));
746 }
747
748 // dependency setting might have been changed by the user, so rebuild.
749 Jenkins.getInstance().rebuildDependencyGraph();
750
751 // reflect the submission of the pseudo 'upstream build trriger'.
752 // this needs to be done after we release the lock on 'this',
753 // or otherwise we could dead-lock
754
755 for (AbstractProject<?,?> p : Jenkins.getInstance().getAllItems(AbstractProject.class)) {
756 // Don't consider child projects such as MatrixConfiguration:
757 if (!p.isConfigurable()) continue;
758 boolean isUpstream = upstream.contains(p);
759 synchronized(p) {
760 // does 'p' include us in its BuildTrigger?
761 DescribableList<Publisher,Descriptor<Publisher>> pl = p.getPublishersList();
762 BuildTrigger trigger = pl.get(BuildTrigger.class);
763 List<AbstractProject> newChildProjects = trigger == null ? new ArrayList<AbstractProject>():trigger.getChildProjects(p);
764 if(isUpstream) {
765 if(!newChildProjects.contains(this))
766 newChildProjects.add(this);
767 } else {
768 newChildProjects.remove(this);
769 }
770
771 if(newChildProjects.isEmpty()) {
772 pl.remove(BuildTrigger.class);
773 } else {
774 // here, we just need to replace the old one with the new one,
775 // but there was a regression (we don't know when it started) that put multiple BuildTriggers
776 // into the list.
777 // for us not to lose the data, we need to merge them all.
778 List<BuildTrigger> existingList = pl.getAll(BuildTrigger.class);
779 BuildTrigger existing;
780 switch (existingList.size()) {
781 case 0:
782 existing = null;
783 break;
784 case 1:
785 existing = existingList.get(0);
786 break;
787 default:
788 pl.removeAll(BuildTrigger.class);
789 Set<AbstractProject> combinedChildren = new HashSet<AbstractProject>();
790 for (BuildTrigger bt : existingList)
791 combinedChildren.addAll(bt.getChildProjects(p));
792 existing = new BuildTrigger(new ArrayList<AbstractProject>(combinedChildren),existingList.get(0).getThreshold());
793 pl.add(existing);
794 break;
795 }
796
797 if(existing!=null && existing.hasSame(p,newChildProjects))
798 continue; // no need to touch
799 pl.replace(new BuildTrigger(newChildProjects,
800 existing==null?Result.SUCCESS:existing.getThreshold()));
801 }
802 }
803 }
804
805 // notify the queue as the project might be now tied to different node
806 Jenkins.getInstance().getQueue().scheduleMaintenance();
807
808 // this is to reflect the upstream build adjustments done above
809 Jenkins.getInstance().rebuildDependencyGraph();
810 }
811
812 /**
813 * @deprecated
814 * Use {@link #scheduleBuild(Cause)}. Since 1.283
815 */
816 public boolean scheduleBuild() {
817 return scheduleBuild(new LegacyCodeCause());
818 }
819
820 /**
821 * @deprecated
822 * Use {@link #scheduleBuild(int, Cause)}. Since 1.283
823 */
824 public boolean scheduleBuild(int quietPeriod) {
825 return scheduleBuild(quietPeriod, new LegacyCodeCause());
826 }
827
828 /**
829 * Schedules a build of this project.
830 *
831 * @return
832 * true if the project is actually added to the queue.
833 * false if the queue contained it and therefore the add()
834 * was noop
835 */
836 public boolean scheduleBuild(Cause c) {
837 return scheduleBuild(getQuietPeriod(), c);
838 }
839
840 public boolean scheduleBuild(int quietPeriod, Cause c) {
841 return scheduleBuild(quietPeriod, c, new Action[0]);
842 }
843
844 /**
845 * Schedules a build.
846 *
847 * Important: the actions should be persistable without outside references (e.g. don't store
848 * references to this project). To provide parameters for a parameterized project, add a ParametersAction. If
849 * no ParametersAction is provided for such a project, one will be created with the default parameter values.
850 *
851 * @param quietPeriod the quiet period to observer
852 * @param c the cause for this build which should be recorded
853 * @param actions a list of Actions that will be added to the build
854 * @return whether the build was actually scheduled
855 */
856 public boolean scheduleBuild(int quietPeriod, Cause c, Action... actions) {
857 return scheduleBuild2(quietPeriod,c,actions)!=null;
858 }
859
860 /**
861 * Schedules a build of this project, and returns a {@link Future} object
862 * to wait for the completion of the build.
863 *
864 * @param actions
865 * For the convenience of the caller, this array can contain null, and those will be silently ignored.
866 */
867 @WithBridgeMethods(Future.class)
868 public QueueTaskFuture<R> scheduleBuild2(int quietPeriod, Cause c, Action... actions) {
869 return scheduleBuild2(quietPeriod,c,Arrays.asList(actions));
870 }
871
872 /**
873 * Schedules a build of this project, and returns a {@link Future} object
874 * to wait for the completion of the build.
875 *
876 * @param actions
877 * For the convenience of the caller, this collection can contain null, and those will be silently ignored.
878 * @since 1.383
879 */
880 @SuppressWarnings("unchecked")
881 @WithBridgeMethods(Future.class)
882 public QueueTaskFuture<R> scheduleBuild2(int quietPeriod, Cause c, Collection<? extends Action> actions) {
883 if (!isBuildable())
884 return null;
885
886 List<Action> queueActions = new ArrayList<Action>(actions);
887 if (isParameterized() && Util.filter(queueActions, ParametersAction.class).isEmpty()) {
888 queueActions.add(new ParametersAction(getDefaultParametersValues()));
889 }
890
891 if (c != null) {
892 queueActions.add(new CauseAction(c));
893 }
894
895 WaitingItem i = Jenkins.getInstance().getQueue().schedule(this, quietPeriod, queueActions);
896 if(i!=null)
897 return (QueueTaskFuture)i.getFuture();
898 return null;
899 }
900
901 private List<ParameterValue> getDefaultParametersValues() {
902 ParametersDefinitionProperty paramDefProp = getProperty(ParametersDefinitionProperty.class);
903 ArrayList<ParameterValue> defValues = new ArrayList<ParameterValue>();
904
905 /*
906 * This check is made ONLY if someone will call this method even if isParametrized() is false.
907 */
908 if(paramDefProp == null)
909 return defValues;
910
911 /* Scan for all parameter with an associated default values */
912 for(ParameterDefinition paramDefinition : paramDefProp.getParameterDefinitions())
913 {
914 ParameterValue defaultValue = paramDefinition.getDefaultParameterValue();
915
916 if(defaultValue != null)
917 defValues.add(defaultValue);
918 }
919
920 return defValues;
921 }
922
923 /**
924 * Schedules a build, and returns a {@link Future} object
925 * to wait for the completion of the build.
926 *
927 * <p>
928 * Production code shouldn't be using this, but for tests this is very convenient, so this isn't marked
929 * as deprecated.
930 */
931 @SuppressWarnings("deprecation")
932 @WithBridgeMethods(Future.class)
933 public QueueTaskFuture<R> scheduleBuild2(int quietPeriod) {
934 return scheduleBuild2(quietPeriod, new LegacyCodeCause());
935 }
936
937 /**
938 * Schedules a build of this project, and returns a {@link Future} object
939 * to wait for the completion of the build.
940 */
941 @WithBridgeMethods(Future.class)
942 public QueueTaskFuture<R> scheduleBuild2(int quietPeriod, Cause c) {
943 return scheduleBuild2(quietPeriod, c, new Action[0]);
944 }
945
946 /**
947 * Schedules a polling of this project.
948 */
949 public boolean schedulePolling() {
950 if(isDisabled()) return false;
951 SCMTrigger scmt = getTrigger(SCMTrigger.class);
952 if(scmt==null) return false;
953 scmt.run();
954 return true;
955 }
956
957 /**
958 * Returns true if the build is in the queue.
959 */
960 @Override
961 public boolean isInQueue() {
962 return Jenkins.getInstance().getQueue().contains(this);
963 }
964
965 @Override
966 public Queue.Item getQueueItem() {
967 return Jenkins.getInstance().getQueue().getItem(this);
968 }
969
970 /**
971 * Gets the JDK that this project is configured with, or null.
972 */
973 public JDK getJDK() {
974 return Jenkins.getInstance().getJDK(jdk);
975 }
976
977 /**
978 * Overwrites the JDK setting.
979 */
980 public void setJDK(JDK jdk) throws IOException {
981 this.jdk = jdk.getName();
982 save();
983 }
984
985 public BuildAuthorizationToken getAuthToken() {
986 return authToken;
987 }
988
989 @Override
990 public RunMap<R> _getRuns() {
991 assert builds.baseDirInitialized() : "neither onCreatedFromScratch nor onLoad called on " + this + " yet";
992 return builds;
993 }
994
995 @Override
996 public void removeRun(R run) {
997 this.builds.remove(run);
998 }
999
1000 /**
1001 * {@inheritDoc}
1002 *
1003 * More efficient implementation.
1004 */
1005 @Override
1006 public R getBuild(String id) {
1007 return builds.getById(id);
1008 }
1009
1010 /**
1011 * {@inheritDoc}
1012 *
1013 * More efficient implementation.
1014 */
1015 @Override
1016 public R getBuildByNumber(int n) {
1017 return builds.getByNumber(n);
1018 }
1019
1020 /**
1021 * {@inheritDoc}
1022 *
1023 * More efficient implementation.
1024 */
1025 @Override
1026 public R getFirstBuild() {
1027 return builds.oldestBuild();
1028 }
1029
1030 @Override
1031 public R getLastBuild() {
1032 return builds.newestBuild();
1033 }
1034
1035 @Override
1036 public R getNearestBuild(int n) {
1037 return builds.search(n, Direction.ASC);
1038 }
1039
1040 @Override
1041 public R getNearestOldBuild(int n) {
1042 return builds.search(n, Direction.DESC);
1043 }
1044
1045 /**
1046 * Determines Class&lt;R>.
1047 */
1048 protected abstract Class<R> getBuildClass();
1049
1050 // keep track of the previous time we started a build
1051 private transient long lastBuildStartTime;
1052
1053 /**
1054 * Creates a new build of this project for immediate execution.
1055 */
1056 protected synchronized R newBuild() throws IOException {
1057 // make sure we don't start two builds in the same second
1058 // so the build directories will be different too
1059 long timeSinceLast = System.currentTimeMillis() - lastBuildStartTime;
1060 if (timeSinceLast < 1000) {
1061 try {
1062 Thread.sleep(1000 - timeSinceLast);
1063 } catch (InterruptedException e) {
1064 }
1065 }
1066 lastBuildStartTime = System.currentTimeMillis();
1067 try {
1068 R lastBuild = getBuildClass().getConstructor(getClass()).newInstance(this);
1069 builds.put(lastBuild);
1070 return lastBuild;
1071 } catch (InstantiationException e) {
1072 throw new Error(e);
1073 } catch (IllegalAccessException e) {
1074 throw new Error(e);
1075 } catch (InvocationTargetException e) {
1076 throw handleInvocationTargetException(e);
1077 } catch (NoSuchMethodException e) {
1078 throw new Error(e);
1079 }
1080 }
1081
1082 private IOException handleInvocationTargetException(InvocationTargetException e) {
1083 Throwable t = e.getTargetException();
1084 if(t instanceof Error) throw (Error)t;
1085 if(t instanceof RuntimeException) throw (RuntimeException)t;
1086 if(t instanceof IOException) return (IOException)t;
1087 throw new Error(t);
1088 }
1089
1090 /**
1091 * Loads an existing build record from disk.
1092 */
1093 protected R loadBuild(File dir) throws IOException {
1094 try {
1095 return getBuildClass().getConstructor(getClass(),File.class).newInstance(this,dir);
1096 } catch (InstantiationException e) {
1097 throw new Error(e);
1098 } catch (IllegalAccessException e) {
1099 throw new Error(e);
1100 } catch (InvocationTargetException e) {
1101 throw handleInvocationTargetException(e);
1102 } catch (NoSuchMethodException e) {
1103 throw new Error(e);
1104 }
1105 }
1106
1107 /**
1108 * {@inheritDoc}
1109 *
1110 * <p>
1111 * Note that this method returns a read-only view of {@link Action}s.
1112 * {@link BuildStep}s and others who want to add a project action
1113 * should do so by implementing {@link BuildStep#getProjectActions(AbstractProject)}.
1114 *
1115 * @see TransientProjectActionFactory
1116 */
1117 @Override
1118 public List<Action> getActions() {
1119 // add all the transient actions, too
1120 List<Action> actions = new Vector<Action>(super.getActions());
1121 actions.addAll(transientActions);
1122 // return the read only list to cause a failure on plugins who try to add an action here
1123 return Collections.unmodifiableList(actions);
1124 }
1125
1126 /**
1127 * Gets the {@link Node} where this project was last built on.
1128 *
1129 * @return
1130 * null if no information is available (for example,
1131 * if no build was done yet.)
1132 */
1133 public Node getLastBuiltOn() {
1134 // where was it built on?
1135 AbstractBuild b = getLastBuild();
1136 if(b==null)
1137 return null;
1138 else
1139 return b.getBuiltOn();
1140 }
1141
1142 public Object getSameNodeConstraint() {
1143 return this; // in this way, any member that wants to run with the main guy can nominate the project itself
1144 }
1145
1146 public final Task getOwnerTask() {
1147 return this;
1148 }
1149
1150 /**
1151 * {@inheritDoc}
1152 *
1153 * <p>
1154 * A project must be blocked if its own previous build is in progress,
1155 * or if the blockBuildWhenUpstreamBuilding option is true and an upstream
1156 * project is building, but derived classes can also check other conditions.
1157 */
1158 public boolean isBuildBlocked() {
1159 return getCauseOfBlockage()!=null;
1160 }
1161
1162 public String getWhyBlocked() {
1163 CauseOfBlockage cb = getCauseOfBlockage();
1164 return cb!=null ? cb.getShortDescription() : null;
1165 }
1166
1167 /**
1168 * Blocked because the previous build is already in progress.
1169 */
1170 public static class BecauseOfBuildInProgress extends CauseOfBlockage {
1171 private final AbstractBuild<?,?> build;
1172
1173 public BecauseOfBuildInProgress(AbstractBuild<?, ?> build) {
1174 this.build = build;
1175 }
1176
1177 @Override
1178 public String getShortDescription() {
1179 Executor e = build.getExecutor();
1180 String eta = "";
1181 if (e != null)
1182 eta = Messages.AbstractProject_ETA(e.getEstimatedRemainingTime());
1183 int lbn = build.getNumber();
1184 return Messages.AbstractProject_BuildInProgress(lbn, eta);
1185 }
1186 }
1187
1188 /**
1189 * Because the downstream build is in progress, and we are configured to wait for that.
1190 */
1191 public static class BecauseOfDownstreamBuildInProgress extends CauseOfBlockage {
1192 public final AbstractProject<?,?> up;
1193
1194 public BecauseOfDownstreamBuildInProgress(AbstractProject<?,?> up) {
1195 this.up = up;
1196 }
1197
1198 @Override
1199 public String getShortDescription() {
1200 return Messages.AbstractProject_DownstreamBuildInProgress(up.getName());
1201 }
1202 }
1203
1204 /**
1205 * Because the upstream build is in progress, and we are configured to wait for that.
1206 */
1207 public static class BecauseOfUpstreamBuildInProgress extends CauseOfBlockage {
1208 public final AbstractProject<?,?> up;
1209
1210 public BecauseOfUpstreamBuildInProgress(AbstractProject<?,?> up) {
1211 this.up = up;
1212 }
1213
1214 @Override
1215 public String getShortDescription() {
1216 return Messages.AbstractProject_UpstreamBuildInProgress(up.getName());
1217 }
1218 }
1219
1220 public CauseOfBlockage getCauseOfBlockage() {
1221 // Block builds until they are done with post-production
1222 if (isLogUpdated() && !isConcurrentBuild())
1223 return new BecauseOfBuildInProgress(getLastBuild());
1224 if (blockBuildWhenDownstreamBuilding()) {
1225 AbstractProject<?,?> bup = getBuildingDownstream();
1226 if (bup!=null)
1227 return new BecauseOfDownstreamBuildInProgress(bup);
1228 }
1229 if (blockBuildWhenUpstreamBuilding()) {
1230 AbstractProject<?,?> bup = getBuildingUpstream();
1231 if (bup!=null)
1232 return new BecauseOfUpstreamBuildInProgress(bup);
1233 }
1234 return null;
1235 }
1236
1237 /**
1238 * Returns the project if any of the downstream project is either
1239 * building, waiting, pending or buildable.
1240 * <p>
1241 * This means eventually there will be an automatic triggering of
1242 * the given project (provided that all builds went smoothly.)
1243 */
1244 public AbstractProject getBuildingDownstream() {
1245 Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
1246
1247 for (AbstractProject tup : getTransitiveDownstreamProjects()) {
1248 if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
1249 return tup;
1250 }
1251 return null;
1252 }
1253
1254 /**
1255 * Returns the project if any of the upstream project is either
1256 * building or is in the queue.
1257 * <p>
1258 * This means eventually there will be an automatic triggering of
1259 * the given project (provided that all builds went smoothly.)
1260 */
1261 public AbstractProject getBuildingUpstream() {
1262 Set<Task> unblockedTasks = Jenkins.getInstance().getQueue().getUnblockedTasks();
1263
1264 for (AbstractProject tup : getTransitiveUpstreamProjects()) {
1265 if (tup!=this && (tup.isBuilding() || unblockedTasks.contains(tup)))
1266 return tup;
1267 }
1268 return null;
1269 }
1270
1271 public List<SubTask> getSubTasks() {
1272 List<SubTask> r = new ArrayList<SubTask>();
1273 r.add(this);
1274 for (SubTaskContributor euc : SubTaskContributor.all())
1275 r.addAll(euc.forProject(this));
1276 for (JobProperty<? super P> p : properties)
1277 r.addAll(p.getSubTasks());
1278 return r;
1279 }
1280
1281 public R createExecutable() throws IOException {
1282 if(isDisabled()) return null;
1283 return newBuild();
1284 }
1285
1286 public void checkAbortPermission() {
1287 checkPermission(AbstractProject.ABORT);
1288 }
1289
1290 public boolean hasAbortPermission() {
1291 return hasPermission(AbstractProject.ABORT);
1292 }
1293
1294 /**
1295 * Gets the {@link Resource} that represents the workspace of this project.
1296 * Useful for locking and mutual exclusion control.
1297 *
1298 * @deprecated as of 1.319
1299 * Projects no longer have a fixed workspace, ands builds will find an available workspace via
1300 * {@link WorkspaceList} for each build (furthermore, that happens after a build is started.)
1301 * So a {@link Resource} representation for a workspace at the project level no longer makes sense.
1302 *
1303 * <p>
1304 * If you need to lock a workspace while you do some computation, see the source code of
1305 * {@link #pollSCMChanges(TaskListener)} for how to obtain a lock of a workspace through {@link WorkspaceList}.
1306 */
1307 public Resource getWorkspaceResource() {
1308 return new Resource(getFullDisplayName()+" workspace");
1309 }
1310
1311 /**
1312 * List of necessary resources to perform the build of this project.
1313 */
1314 public ResourceList getResourceList() {
1315 final Set<ResourceActivity> resourceActivities = getResourceActivities();
1316 final List<ResourceList> resourceLists = new ArrayList<ResourceList>(1 + resourceActivities.size());
1317 for (ResourceActivity activity : resourceActivities) {
1318 if (activity != this && activity != null) {
1319 // defensive infinite recursion and null check
1320 resourceLists.add(activity.getResourceList());
1321 }
1322 }
1323 return ResourceList.union(resourceLists);
1324 }
1325
1326 /**
1327 * Set of child resource activities of the build of this project (override in child projects).
1328 * @return The set of child resource activities of the build of this project.
1329 */
1330 protected Set<ResourceActivity> getResourceActivities() {
1331 return Collections.emptySet();
1332 }
1333
1334 public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException, InterruptedException {
1335 SCM scm = getScm();
1336 if(scm==null)
1337 return true; // no SCM
1338
1339 FilePath workspace = build.getWorkspace();
1340 workspace.mkdirs();
1341
1342 boolean r = scm.checkout(build, launcher, workspace, listener, changelogFile);
1343 if (r) {
1344 // Only calcRevisionsFromBuild if checkout was successful. Note that modern SCM implementations
1345 // won't reach this line anyway, as they throw AbortExceptions on checkout failure.
1346 calcPollingBaseline(build, launcher, listener);
1347 }
1348 return r;
1349 }
1350
1351 /**
1352 * Pushes the baseline up to the newly checked out revision.
1353 */
1354 private void calcPollingBaseline(AbstractBuild build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
1355 SCMRevisionState baseline = build.getAction(SCMRevisionState.class);
1356 if (baseline==null) {
1357 try {
1358 baseline = getScm()._calcRevisionsFromBuild(build, launcher, listener);
1359 } catch (AbstractMethodError e) {
1360 baseline = SCMRevisionState.NONE; // pre-1.345 SCM implementations, which doesn't use the baseline in polling
1361 }
1362 if (baseline!=null)
1363 build.addAction(baseline);
1364 }
1365 pollingBaseline = baseline;
1366 }
1367
1368 /**
1369 * Checks if there's any update in SCM, and returns true if any is found.
1370 *
1371 * @deprecated as of 1.346
1372 * Use {@link #poll(TaskListener)} instead.
1373 */
1374 public boolean pollSCMChanges( TaskListener listener ) {
1375 return poll(listener).hasChanges();
1376 }
1377
1378 /**
1379 * Checks if there's any update in SCM, and returns true if any is found.
1380 *
1381 * <p>
1382 * The implementation is responsible for ensuring mutual exclusion between polling and builds
1383 * if necessary.
1384 *
1385 * @since 1.345
1386 */
1387 public PollingResult poll( TaskListener listener ) {
1388 SCM scm = getScm();
1389 if (scm==null) {
1390 listener.getLogger().println(Messages.AbstractProject_NoSCM());
1391 return NO_CHANGES;
1392 }
1393 if (!isBuildable()) {
1394 listener.getLogger().println(Messages.AbstractProject_Disabled());
1395 return NO_CHANGES;
1396 }
1397
1398 R lb = getLastBuild();
1399 if (lb==null) {
1400 listener.getLogger().println(Messages.AbstractProject_NoBuilds());
1401 return isInQueue() ? NO_CHANGES : BUILD_NOW;
1402 }
1403
1404 if (pollingBaseline==null) {
1405 R success = getLastSuccessfulBuild(); // if we have a persisted baseline, we'll find it by this
1406 for (R r=lb; r!=null; r=r.getPreviousBuild()) {
1407 SCMRevisionState s = r.getAction(SCMRevisionState.class);
1408 if (s!=null) {
1409 pollingBaseline = s;
1410 break;
1411 }
1412 if (r==success) break; // searched far enough
1413 }
1414 // NOTE-NO-BASELINE:
1415 // if we don't have baseline yet, it means the data is built by old Hudson that doesn't set the baseline
1416 // as action, so we need to compute it. This happens later.
1417 }
1418
1419 try {
1420 SCMPollListener.fireBeforePolling(this, listener);
1421 PollingResult r = _poll(listener, scm, lb);
1422 SCMPollListener.firePollingSuccess(this,listener, r);
1423 return r;
1424 } catch (AbortException e) {
1425 listener.getLogger().println(e.getMessage());
1426 listener.fatalError(Messages.AbstractProject_Aborted());
1427 LOGGER.log(Level.FINE, "Polling "+this+" aborted",e);
1428 SCMPollListener.firePollingFailed(this, listener,e);
1429 return NO_CHANGES;
1430 } catch (IOException e) {
1431 e.printStackTrace(listener.fatalError(e.getMessage()));
1432 SCMPollListener.firePollingFailed(this, listener,e);
1433 return NO_CHANGES;
1434 } catch (InterruptedException e) {
1435 e.printStackTrace(listener.fatalError(Messages.AbstractProject_PollingABorted()));
1436 SCMPollListener.firePollingFailed(this, listener,e);
1437 return NO_CHANGES;
1438 } catch (RuntimeException e) {
1439 SCMPollListener.firePollingFailed(this, listener,e);
1440 throw e;
1441 } catch (Error e) {
1442 SCMPollListener.firePollingFailed(this, listener,e);
1443 throw e;
1444 }
1445 }
1446
1447 /**
1448 * {@link #poll(TaskListener)} method without the try/catch block that does listener notification and .
1449 */
1450 private PollingResult _poll(TaskListener listener, SCM scm, R lb) throws IOException, InterruptedException {
1451 if (scm.requiresWorkspaceForPolling()) {
1452 // lock the workspace of the last build
1453 FilePath ws=lb.getWorkspace();
1454
1455 WorkspaceOfflineReason workspaceOfflineReason = workspaceOffline( lb );
1456 if ( workspaceOfflineReason != null ) {
1457 // workspace offline
1458 for (WorkspaceBrowser browser : Jenkins.getInstance().getExtensionList(WorkspaceBrowser.class)) {
1459 ws = browser.getWorkspace(this);
1460 if (ws != null) {
1461 return pollWithWorkspace(listener, scm, lb, ws, browser.getWorkspaceList());
1462 }
1463 }
1464
1465 // build now, or nothing will ever be built
1466 Label label = getAssignedLabel();
1467 if (label != null && label.isSelfLabel()) {
1468 // if the build is fixed on a node, then attempting a build will do us
1469 // no good. We should just wait for the slave to come back.
1470 listener.getLogger().print(Messages.AbstractProject_NoWorkspace());
1471 listener.getLogger().println( " (" + workspaceOfflineReason.name() + ")");
1472 return NO_CHANGES;
1473 }
1474 listener.getLogger().println( ws==null
1475 ? Messages.AbstractProject_WorkspaceOffline()
1476 : Messages.AbstractProject_NoWorkspace());
1477 if (isInQueue()) {
1478 listener.getLogger().println(Messages.AbstractProject_AwaitingBuildForWorkspace());
1479 return NO_CHANGES;
1480 } else {
1481 listener.getLogger().print(Messages.AbstractProject_NewBuildForWorkspace());
1482 listener.getLogger().println( " (" + workspaceOfflineReason.name() + ")");
1483 return BUILD_NOW;
1484 }
1485 } else {
1486 WorkspaceList l = lb.getBuiltOn().toComputer().getWorkspaceList();
1487 return pollWithWorkspace(listener, scm, lb, ws, l);
1488
1489 }
1490 } else {
1491 // polling without workspace
1492 LOGGER.fine("Polling SCM changes of " + getName());
1493
1494 if (pollingBaseline==null) // see NOTE-NO-BASELINE above
1495 calcPollingBaseline(lb,null,listener);
1496 PollingResult r = scm.poll(this, null, null, listener, pollingBaseline);
1497 pollingBaseline = r.remote;
1498 return r;
1499 }
1500 }
1501
1502 private PollingResult pollWithWorkspace(TaskListener listener, SCM scm, R lb, FilePath ws, WorkspaceList l) throws InterruptedException, IOException {
1503 // if doing non-concurrent build, acquire a workspace in a way that causes builds to block for this workspace.
1504 // this prevents multiple workspaces of the same job --- the behavior of Hudson < 1.319.
1505 //
1506 // OTOH, if a concurrent build is chosen, the user is willing to create a multiple workspace,
1507 // so better throughput is achieved over time (modulo the initial cost of creating that many workspaces)
1508 // by having multiple workspaces
1509 WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
1510 Launcher launcher = ws.createLauncher(listener).decorateByEnv(getEnvironment(lb.getBuiltOn(),listener));
1511 try {
1512 LOGGER.fine("Polling SCM changes of " + getName());
1513 if (pollingBaseline==null) // see NOTE-NO-BASELINE above
1514 calcPollingBaseline(lb,launcher,listener);
1515 PollingResult r = scm.poll(this, launcher, ws, listener, pollingBaseline);
1516 pollingBaseline = r.remote;
1517 return r;
1518 } finally {
1519 lease.release();
1520 }
1521 }
1522
1523 enum WorkspaceOfflineReason {
1524 nonexisting_workspace,
1525 builton_node_gone,
1526 builton_node_no_executors
1527 }
1528
1529 private WorkspaceOfflineReason workspaceOffline(R build) throws IOException, InterruptedException {
1530 FilePath ws = build.getWorkspace();
1531 if (ws==null || !ws.exists()) {
1532 return WorkspaceOfflineReason.nonexisting_workspace;
1533 }
1534
1535 Node builtOn = build.getBuiltOn();
1536 if (builtOn == null) { // node built-on doesn't exist anymore
1537 return WorkspaceOfflineReason.builton_node_gone;
1538 }
1539
1540 if (builtOn.toComputer() == null) { // node still exists, but has 0 executors - o.s.l.t.
1541 return WorkspaceOfflineReason.builton_node_no_executors;
1542 }
1543
1544 return null;
1545 }
1546
1547 /**
1548 * Returns true if this user has made a commit to this project.
1549 *
1550 * @since 1.191
1551 */
1552 public boolean hasParticipant(User user) {
1553 for( R build = getLastBuild(); build!=null; build=build.getPreviousBuild())
1554 if(build.hasParticipant(user))
1555 return true;
1556 return false;
1557 }
1558
1559 @Exported
1560 public SCM getScm() {
1561 return scm;
1562 }
1563
1564 public void setScm(SCM scm) throws IOException {
1565 this.scm = scm;
1566 save();
1567 }
1568
1569 /**
1570 * Adds a new {@link Trigger} to this {@link Project} if not active yet.
1571 */
1572 public void addTrigger(Trigger<?> trigger) throws IOException {
1573 addToList(trigger,triggers());
1574 }
1575
1576 public void removeTrigger(TriggerDescriptor trigger) throws IOException {
1577 removeFromList(trigger,triggers());
1578 }
1579
1580 protected final synchronized <T extends Describable<T>>
1581 void addToList( T item, List<T> collection ) throws IOException {
1582 for( int i=0; i<collection.size(); i++ ) {
1583 if(collection.get(i).getDescriptor()==item.getDescriptor()) {
1584 // replace
1585 collection.set(i,item);
1586 save();
1587 return;
1588 }
1589 }
1590 // add
1591 collection.add(item);
1592 save();
1593 updateTransientActions();
1594 }
1595
1596 protected final synchronized <T extends Describable<T>>
1597 void removeFromList(Descriptor<T> item, List<T> collection) throws IOException {
1598 for( int i=0; i< collection.size(); i++ ) {
1599 if(collection.get(i).getDescriptor()==item) {
1600 // found it
1601 collection.remove(i);
1602 save();
1603 updateTransientActions();
1604 return;
1605 }
1606 }
1607 }
1608
1609 @SuppressWarnings("unchecked")
1610 public synchronized Map<TriggerDescriptor,Trigger> getTriggers() {
1611 return (Map)Descriptor.toMap(triggers());
1612 }
1613
1614 /**
1615 * Gets the specific trigger, or null if the propert is not configured for this job.
1616 */
1617 public <T extends Trigger> T getTrigger(Class<T> clazz) {
1618 for (Trigger p : triggers()) {
1619 if(clazz.isInstance(p))
1620 return clazz.cast(p);
1621 }
1622 return null;
1623 }
1624
1625 //
1626 //
1627 // fingerprint related
1628 //
1629 //
1630 /**
1631 * True if the builds of this project produces {@link Fingerprint} records.
1632 */
1633 public abstract boolean isFingerprintConfigured();
1634
1635 /**
1636 * Gets the other {@link AbstractProject}s that should be built
1637 * when a build of this project is completed.
1638 */
1639 @Exported
1640 public final List<AbstractProject> getDownstreamProjects() {
1641 return Jenkins.getInstance().getDependencyGraph().getDownstream(this);
1642 }
1643
1644 @Exported
1645 public final List<AbstractProject> getUpstreamProjects() {
1646 return Jenkins.getInstance().getDependencyGraph().getUpstream(this);
1647 }
1648
1649 /**
1650 * Returns only those upstream projects that defines {@link BuildTrigger} to this project.
1651 * This is a subset of {@link #getUpstreamProjects()}
1652 *
1653 * @return A List of upstream projects that has a {@link BuildTrigger} to this project.
1654 */
1655 public final List<AbstractProject> getBuildTriggerUpstreamProjects() {
1656 ArrayList<AbstractProject> result = new ArrayList<AbstractProject>();
1657 for (AbstractProject<?,?> ap : getUpstreamProjects()) {
1658 BuildTrigger buildTrigger = ap.getPublishersList().get(BuildTrigger.class);
1659 if (buildTrigger != null)
1660 if (buildTrigger.getChildProjects(ap).contains(this))
1661 result.add(ap);
1662 }
1663 return result;
1664 }
1665
1666 /**
1667 * Gets all the upstream projects including transitive upstream projects.
1668 *
1669 * @since 1.138
1670 */
1671 public final Set<AbstractProject> getTransitiveUpstreamProjects() {
1672 return Jenkins.getInstance().getDependencyGraph().getTransitiveUpstream(this);
1673 }
1674
1675 /**
1676 * Gets all the downstream projects including transitive downstream projects.
1677 *
1678 * @since 1.138
1679 */
1680 public final Set<AbstractProject> getTransitiveDownstreamProjects() {
1681 return Jenkins.getInstance().getDependencyGraph().getTransitiveDownstream(this);
1682 }
1683
1684 /**
1685 * Gets the dependency relationship map between this project (as the source)
1686 * and that project (as the sink.)
1687 *
1688 * @return
1689 * can be empty but not null. build number of this project to the build
1690 * numbers of that project.
1691 */
1692 public SortedMap<Integer, RangeSet> getRelationship(AbstractProject that) {
1693 TreeMap<Integer,RangeSet> r = new TreeMap<Integer,RangeSet>(REVERSE_INTEGER_COMPARATOR);
1694
1695 checkAndRecord(that, r, this.getBuilds());
1696 // checkAndRecord(that, r, that.getBuilds());
1697
1698 return r;
1699 }
1700
1701 /**
1702 * Helper method for getDownstreamRelationship.
1703 *
1704 * For each given build, find the build number range of the given project and put that into the map.
1705 */
1706 private void checkAndRecord(AbstractProject that, TreeMap<Integer, RangeSet> r, Collection<R> builds) {
1707 for (R build : builds) {
1708 RangeSet rs = build.getDownstreamRelationship(that);
1709 if(rs==null || rs.isEmpty())
1710 continue;
1711
1712 int n = build.getNumber();
1713
1714 RangeSet value = r.get(n);
1715 if(value==null)
1716 r.put(n,rs);
1717 else
1718 value.add(rs);
1719 }
1720 }
1721
1722 /**
1723 * Builds the dependency graph.
1724 * @see DependencyGraph
1725 */
1726 protected abstract void buildDependencyGraph(DependencyGraph graph);
1727
1728 @Override
1729 protected SearchIndexBuilder makeSearchIndex() {
1730 SearchIndexBuilder sib = super.makeSearchIndex();
1731 if(isBuildable() && hasPermission(Jenkins.ADMINISTER))
1732 sib.add("build","build");
1733 return sib;
1734 }
1735
1736 @Override
1737 protected HistoryWidget createHistoryWidget() {
1738 return new BuildHistoryWidget<R>(this,builds,HISTORY_ADAPTER);
1739 }
1740
1741 public boolean isParameterized() {
1742 return getProperty(ParametersDefinitionProperty.class) != null;
1743 }
1744
1745 //
1746 //
1747 // actions
1748 //
1749 //
1750 /**
1751 * Schedules a new build command.
1752 */
1753 public void doBuild( StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay ) throws IOException, ServletException {
1754 if (delay==null) delay=new TimeDuration(getQuietPeriod());
1755
1756 // if a build is parameterized, let that take over
1757 ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
1758 if (pp != null && !req.getMethod().equals("POST")) {
1759 // show the parameter entry form.
1760 req.getView(pp, "index.jelly").forward(req, rsp);
1761 return;
1762 }
1763
1764 BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
1765
1766 if (pp != null) {
1767 pp._doBuild(req,rsp,delay);
1768 return;
1769 }
1770
1771 if (!isBuildable())
1772 throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR,new IOException(getFullName()+" is not buildable"));
1773
1774 Jenkins.getInstance().getQueue().schedule(this, (int)delay.getTime(), getBuildCause(req));
1775 rsp.sendRedirect(".");
1776 }
1777
1778 /**
1779 * Computes the build cause, using RemoteCause or UserCause as appropriate.
1780 */
1781 /*package*/ CauseAction getBuildCause(StaplerRequest req) {
1782 Cause cause;
1783 if (authToken != null && authToken.getToken() != null && req.getParameter("token") != null) {
1784 // Optional additional cause text when starting via token
1785 String causeText = req.getParameter("cause");
1786 cause = new RemoteCause(req.getRemoteAddr(), causeText);
1787 } else {
1788 cause = new UserIdCause();
1789 }
1790 return new CauseAction(cause);
1791 }
1792
1793 /**
1794 * Computes the delay by taking the default value and the override in the request parameter into the account.
1795 *
1796 * @deprecated as of 1.488
1797 * Inject {@link TimeDuration}.
1798 */
1799 public int getDelay(StaplerRequest req) throws ServletException {
1800 String delay = req.getParameter("delay");
1801 if (delay==null) return getQuietPeriod();
1802
1803 try {
1804 // TODO: more unit handling
1805 if(delay.endsWith("sec")) delay=delay.substring(0,delay.length()-3);
1806 if(delay.endsWith("secs")) delay=delay.substring(0,delay.length()-4);
1807 return Integer.parseInt(delay);
1808 } catch (NumberFormatException e) {
1809 throw new ServletException("Invalid delay parameter value: "+delay);
1810 }
1811 }
1812
1813 /**
1814 * Supports build trigger with parameters via an HTTP GET or POST.
1815 * Currently only String parameters are supported.
1816 */
1817 public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp, @QueryParameter TimeDuration delay) throws IOException, ServletException {
1818 BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
1819
1820 ParametersDefinitionProperty pp = getProperty(ParametersDefinitionProperty.class);
1821 if (pp != null) {
1822 pp.buildWithParameters(req,rsp,delay);
1823 } else {
1824 throw new IllegalStateException("This build is not parameterized!");
1825 }
1826
1827 }
1828
1829 /**
1830 * Schedules a new SCM polling command.
1831 */
1832 public void doPolling( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1833 BuildAuthorizationToken.checkPermission(this, authToken, req, rsp);
1834 schedulePolling();
1835 rsp.sendRedirect(".");
1836 }
1837
1838 /**
1839 * Cancels a scheduled build.
1840 */
1841 @RequirePOST
1842 public void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
1843 checkPermission(ABORT);
1844
1845 Jenkins.getInstance().getQueue().cancel(this);
1846 rsp.forwardToPreviousPage(req);
1847 }
1848
1849 /**
1850 * Deletes this project.
1851 */
1852 @Override
1853 @RequirePOST
1854 public void doDoDelete(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, InterruptedException {
1855 delete();
1856 if (req == null || rsp == null)
1857 return;
1858 View view = req.findAncestorObject(View.class);
1859 if (view == null)
1860 rsp.sendRedirect2(req.getContextPath() + '/' + getParent().getUrl());
1861 else
1862 rsp.sendRedirect2(req.getContextPath() + '/' + view.getUrl());
1863 }
1864
1865 @Override
1866 protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
1867 super.submit(req,rsp);
1868 JSONObject json = req.getSubmittedForm();
1869
1870 makeDisabled(req.getParameter("disable")!=null);
1871
1872 jdk = req.getParameter("jdk");
1873 if(req.getParameter("hasCustomQuietPeriod")!=null) {
1874 quietPeriod = Integer.parseInt(req.getParameter("quiet_period"));
1875 } else {
1876 quietPeriod = null;
1877 }
1878 if(req.getParameter("hasCustomScmCheckoutRetryCount")!=null) {
1879 scmCheckoutRetryCount = Integer.parseInt(req.getParameter("scmCheckoutRetryCount"));
1880 } else {
1881 scmCheckoutRetryCount = null;
1882 }
1883 blockBuildWhenDownstreamBuilding = req.getParameter("blockBuildWhenDownstreamBuilding")!=null;
1884 blockBuildWhenUpstreamBuilding = req.getParameter("blockBuildWhenUpstreamBuilding")!=null;
1885
1886 if(req.hasParameter("customWorkspace")) {
1887 customWorkspace = Util.fixEmptyAndTrim(req.getParameter("customWorkspace.directory"));
1888 } else {
1889 customWorkspace = null;
1890 }
1891
1892 if (json.has("scmCheckoutStrategy"))
1893 scmCheckoutStrategy = req.bindJSON(SCMCheckoutStrategy.class,
1894 json.getJSONObject("scmCheckoutStrategy"));
1895 else
1896 scmCheckoutStrategy = null;
1897
1898
1899 if(req.getParameter("hasSlaveAffinity")!=null) {
1900 assignedNode = Util.fixEmptyAndTrim(req.getParameter("_.assignedLabelString"));
1901 } else {
1902 assignedNode = null;
1903 }
1904 canRoam = assignedNode==null;
1905
1906 concurrentBuild = req.getSubmittedForm().has("concurrentBuild");
1907
1908 authToken = BuildAuthorizationToken.create(req);
1909
1910 setScm(SCMS.parseSCM(req,this));
1911
1912 for (Trigger t : triggers())
1913 t.stop();
1914 triggers = buildDescribable(req, Trigger.for_(this));
1915 for (Trigger t : triggers)
1916 t.start(this,true);
1917
1918 for (Publisher _t : Descriptor.newInstancesFromHeteroList(req, json, "publisher", Jenkins.getInstance().getExtensionList(BuildTrigger.DescriptorImpl.class))) {
1919 BuildTrigger t = (BuildTrigger) _t;
1920 for (AbstractProject downstream : t.getChildProjects(this)) {
1921 downstream.checkPermission(BUILD);
1922 }
1923 }
1924 }
1925
1926 /**
1927 * @deprecated
1928 * As of 1.261. Use {@link #buildDescribable(StaplerRequest, List)} instead.
1929 */
1930 protected final <T extends Describable<T>> List<T> buildDescribable(StaplerRequest req, List<? extends Descriptor<T>> descriptors, String prefix) throws FormException, ServletException {
1931 return buildDescribable(req,descriptors);
1932 }
1933
1934 protected final <T extends Describable<T>> List<T> buildDescribable(StaplerRequest req, List<? extends Descriptor<T>> descriptors)
1935 throws FormException, ServletException {
1936
1937 JSONObject data = req.getSubmittedForm();
1938 List<T> r = new Vector<T>();
1939 for (Descriptor<T> d : descriptors) {
1940 String safeName = d.getJsonSafeClassName();
1941 if (req.getParameter(safeName) != null) {
1942 T instance = d.newInstance(req, data.getJSONObject(safeName));
1943 r.add(instance);
1944 }
1945 }
1946 return r;
1947 }
1948
1949 /**
1950 * Serves the workspace files.
1951 */
1952 public DirectoryBrowserSupport doWs( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
1953 checkPermission(AbstractProject.WORKSPACE);
1954 FilePath ws = getSomeWorkspace();
1955 if ((ws == null) || (!ws.exists())) {
1956 // if there's no workspace, report a nice error message
1957 // Would be good if when asked for *plain*, do something else!
1958 // (E.g. return 404, or send empty doc.)
1959 // Not critical; client can just check if content type is not text/plain,
1960 // which also serves to detect old versions of Hudson.
1961 req.getView(this,"noWorkspace.jelly").forward(req,rsp);
1962 return null;
1963 } else {
1964 return new DirectoryBrowserSupport(this, ws, getDisplayName()+" workspace", "folder.png", true);
1965 }
1966 }
1967
1968 /**
1969 * Wipes out the workspace.
1970 */
1971 public HttpResponse doDoWipeOutWorkspace() throws IOException, ServletException, InterruptedException {
1972 checkPermission(Functions.isWipeOutPermissionEnabled() ? WIPEOUT : BUILD);
1973 R b = getSomeBuildWithWorkspace();
1974 FilePath ws = b!=null ? b.getWorkspace() : null;
1975 if (ws!=null && getScm().processWorkspaceBeforeDeletion(this, ws, b.getBuiltOn())) {
1976 ws.deleteRecursive();
1977 for (WorkspaceListener wl : WorkspaceListener.all()) {
1978 wl.afterDelete(this);
1979 }
1980 return new HttpRedirect(".");
1981 } else {
1982 // If we get here, that means the SCM blocked the workspace deletion.
1983 return new ForwardToView(this,"wipeOutWorkspaceBlocked.jelly");
1984 }
1985 }
1986
1987 @CLIMethod(name="disable-job")
1988 @RequirePOST
1989 public HttpResponse doDisable() throws IOException, ServletException {
1990 checkPermission(CONFIGURE);
1991 makeDisabled(true);
1992 return new HttpRedirect(".");
1993 }
1994
1995 @CLIMethod(name="enable-job")
1996 @RequirePOST
1997 public HttpResponse doEnable() throws IOException, ServletException {
1998 checkPermission(CONFIGURE);
1999 makeDisabled(false);
2000 return new HttpRedirect(".");
2001 }
2002
2003 /**
2004 * RSS feed for changes in this project.
2005 */
2006 public void doRssChangelog( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
2007 class FeedItem {
2008 ChangeLogSet.Entry e;
2009 int idx;
2010
2011 public FeedItem(Entry e, int idx) {
2012 this.e = e;
2013 this.idx = idx;
2014 }
2015
2016 AbstractBuild<?,?> getBuild() {
2017 return e.getParent().build;
2018 }
2019 }
2020
2021 List<FeedItem> entries = new ArrayList<FeedItem>();
2022
2023 for(R r=getLastBuild(); r!=null; r=r.getPreviousBuild()) {
2024 int idx=0;
2025 for( ChangeLogSet.Entry e : r.getChangeSet())
2026 entries.add(new FeedItem(e,idx++));
2027 }
2028
2029 RSS.forwardToRss(
2030 getDisplayName()+' '+getScm().getDescriptor().getDisplayName()+" changes",
2031 getUrl()+"changes",
2032 entries, new FeedAdapter<FeedItem>() {
2033 public String getEntryTitle(FeedItem item) {
2034 return "#"+item.getBuild().number+' '+item.e.getMsg()+" ("+item.e.getAuthor()+")";
2035 }
2036
2037 public String getEntryUrl(FeedItem item) {
2038 return item.getBuild().getUrl()+"changes#detail"+item.idx;
2039 }
2040
2041 public String getEntryID(FeedItem item) {
2042 return getEntryUrl(item);
2043 }
2044
2045 public String getEntryDescription(FeedItem item) {
2046 StringBuilder buf = new StringBuilder();
2047 for(String path : item.e.getAffectedPaths())
2048 buf.append(path).append('\n');
2049 return buf.toString();
2050 }
2051
2052 public Calendar getEntryTimestamp(FeedItem item) {
2053 return item.getBuild().getTimestamp();
2054 }
2055
2056 public String getEntryAuthor(FeedItem entry) {
2057 return JenkinsLocationConfiguration.get().getAdminAddress();
2058 }
2059 },
2060 req, rsp );
2061 }
2062
2063 /**
2064 * {@link AbstractProject} subtypes should implement this base class as a descriptor.
2065 *
2066 * @since 1.294
2067 */
2068 public static abstract class AbstractProjectDescriptor extends TopLevelItemDescriptor {
2069 /**
2070 * {@link AbstractProject} subtypes can override this method to veto some {@link Descriptor}s
2071 * from showing up on their configuration screen. This is often useful when you are building
2072 * a workflow/company specific project type, where you want to limit the number of choices
2073 * given to the users.
2074 *
2075 * <p>
2076 * Some {@link Descriptor}s define their own schemes for controlling applicability
2077 * (such as {@link BuildStepDescriptor#isApplicable(Class)}),
2078 * This method works like AND in conjunction with them;
2079 * Both this method and that method need to return true in order for a given {@link Descriptor}
2080 * to show up for the given {@link Project}.
2081 *
2082 * <p>
2083 * The default implementation returns true for everything.
2084 *
2085 * @see BuildStepDescriptor#isApplicable(Class)
2086 * @see BuildWrapperDescriptor#isApplicable(AbstractProject)
2087 * @see TriggerDescriptor#isApplicable(Item)
2088 */
2089 @Override
2090 public boolean isApplicable(Descriptor descriptor) {
2091 return true;
2092 }
2093
2094 public FormValidation doCheckAssignedLabelString(@QueryParameter String value) {
2095 if (Util.fixEmpty(value)==null)
2096 return FormValidation.ok(); // nothing typed yet
2097 try {
2098 Label.parseExpression(value);
2099 } catch (ANTLRException e) {
2100 return FormValidation.error(e,
2101 Messages.AbstractProject_AssignedLabelString_InvalidBooleanExpression(e.getMessage()));
2102 }
2103 Label l = Jenkins.getInstance().getLabel(value);
2104 if (l.isEmpty()) {
2105 for (LabelAtom a : l.listAtoms()) {
2106 if (a.isEmpty()) {
2107 LabelAtom nearest = LabelAtom.findNearest(a.getName());
2108 return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch_DidYouMean(a.getName(),nearest.getDisplayName()));
2109 }
2110 }
2111 return FormValidation.warning(Messages.AbstractProject_AssignedLabelString_NoMatch());
2112 }
2113 return FormValidation.ok();
2114 }
2115
2116 public FormValidation doCheckCustomWorkspace(@QueryParameter(value="customWorkspace.directory") String customWorkspace){
2117 if(Util.fixEmptyAndTrim(customWorkspace)==null)
2118 return FormValidation.error(Messages.AbstractProject_CustomWorkspaceEmpty());
2119 else
2120 return FormValidation.ok();
2121 }
2122
2123 public AutoCompletionCandidates doAutoCompleteUpstreamProjects(@QueryParameter String value) {
2124 AutoCompletionCandidates candidates = new AutoCompletionCandidates();
2125 List<Job> jobs = Jenkins.getInstance().getItems(Job.class);
2126 for (Job job: jobs) {
2127 if (job.getFullName().startsWith(value)) {
2128 if (job.hasPermission(Item.READ)) {
2129 candidates.add(job.getFullName());
2130 }
2131 }
2132 }
2133 return candidates;
2134 }
2135
2136 public AutoCompletionCandidates doAutoCompleteAssignedLabelString(@QueryParameter String value) {
2137 AutoCompletionCandidates c = new AutoCompletionCandidates();
2138 Set<Label> labels = Jenkins.getInstance().getLabels();
2139 List<String> queries = new AutoCompleteSeeder(value).getSeeds();
2140
2141 for (String term : queries) {
2142 for (Label l : labels) {
2143 if (l.getName().startsWith(term)) {
2144 c.add(l.getName());
2145 }
2146 }
2147 }
2148 return c;
2149 }
2150
2151 public List<SCMCheckoutStrategyDescriptor> getApplicableSCMCheckoutStrategyDescriptors(AbstractProject p) {
2152 return SCMCheckoutStrategyDescriptor._for(p);
2153 }
2154
2155 /**
2156 * Utility class for taking the current input value and computing a list
2157 * of potential terms to match against the list of defined labels.
2158 */
2159 static class AutoCompleteSeeder {
2160 private String source;
2161
2162 AutoCompleteSeeder(String source) {
2163 this.source = source;
2164 }
2165
2166 List<String> getSeeds() {
2167 ArrayList<String> terms = new ArrayList<String>();
2168 boolean trailingQuote = source.endsWith("\"");
2169 boolean leadingQuote = source.startsWith("\"");
2170 boolean trailingSpace = source.endsWith(" ");
2171
2172 if (trailingQuote || (trailingSpace && !leadingQuote)) {
2173 terms.add("");
2174 } else {
2175 if (leadingQuote) {
2176 int quote = source.lastIndexOf('"');
2177 if (quote == 0) {
2178 terms.add(source.substring(1));
2179 } else {
2180 terms.add("");
2181 }
2182 } else {
2183 int space = source.lastIndexOf(' ');
2184 if (space > -1) {
2185 terms.add(source.substring(space+1));
2186 } else {
2187 terms.add(source);
2188 }
2189 }
2190 }
2191
2192 return terms;
2193 }
2194 }
2195 }
2196
2197 /**
2198 * Finds a {@link AbstractProject} that has the name closest to the given name.
2199 */
2200 public static AbstractProject findNearest(String name) {
2201 return findNearest(name,Hudson.getInstance());
2202 }
2203
2204 /**
2205 * Finds a {@link AbstractProject} whose name (when referenced from the specified context) is closest to the given name.
2206 *
2207 * @since 1.419
2208 */
2209 public static AbstractProject findNearest(String name, ItemGroup context) {
2210 List<AbstractProject> projects = Hudson.getInstance().getAllItems(AbstractProject.class);
2211 String[] names = new String[projects.size()];
2212 for( int i=0; i<projects.size(); i++ )
2213 names[i] = projects.get(i).getRelativeNameFrom(context);
2214
2215 String nearest = EditDistance.findNearest(name, names);
2216 return (AbstractProject)Jenkins.getInstance().getItem(nearest,context);
2217 }
2218
2219 private static final Comparator<Integer> REVERSE_INTEGER_COMPARATOR = new Comparator<Integer>() {
2220 public int compare(Integer o1, Integer o2) {
2221 return o2-o1;
2222 }
2223 };
2224
2225 private static final Logger LOGGER = Logger.getLogger(AbstractProject.class.getName());
2226
2227 /**
2228 * Permission to abort a build
2229 */
2230 public static final Permission ABORT = CANCEL;
2231
2232 /**
2233 * Replaceable "Build Now" text.
2234 */
2235 public static final Message<AbstractProject> BUILD_NOW_TEXT = new Message<AbstractProject>();
2236
2237 /**
2238 * Used for CLI binding.
2239 */
2240 @CLIResolver
2241 public static AbstractProject resolveForCLI(
2242 @Argument(required=true,metaVar="NAME",usage="Job name") String name) throws CmdLineException {
2243 AbstractProject item = Jenkins.getInstance().getItemByFullName(name, AbstractProject.class);
2244 if (item==null)
2245 throw new CmdLineException(null,Messages.AbstractItem_NoSuchJobExists(name,AbstractProject.findNearest(name).getFullName()));
2246 return item;
2247 }
2248
2249 public String getCustomWorkspace() {
2250 return customWorkspace;
2251 }
2252
2253 /**
2254 * User-specified workspace directory, or null if it's up to Jenkins.
2255 *
2256 * <p>
2257 * Normally a project uses the workspace location assigned by its parent container,
2258 * but sometimes people have builds that have hard-coded paths.
2259 *
2260 * <p>
2261 * This is not {@link File} because it may have to hold a path representation on another OS.
2262 *
2263 * <p>
2264 * If this path is relative, it's resolved against {@link Node#getRootPath()} on the node where this workspace
2265 * is prepared.
2266 *
2267 * @since 1.410
2268 */
2269 public void setCustomWorkspace(String customWorkspace) throws IOException {
2270 this.customWorkspace= Util.fixEmptyAndTrim(customWorkspace);
2271 save();
2272 }
2273
2274 }
2275