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

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5 * Erik Ramfelt, Koichi Fujikawa, Red Hat, Inc., Seiji Sogabe,
6 * Stephen Connolly, Tom Huybrechts, Yahoo! Inc., Alan Harder, CloudBees, Inc.,
7 * Yahoo!, Inc.
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
26 */
27 package jenkins.model;
28
29 import com.google.common.collect.Lists;
30 import com.google.inject.Injector;
31 import hudson.ExtensionComponent;
32 import hudson.ExtensionFinder;
33 import hudson.model.LoadStatistics;
34 import hudson.model.Messages;
35 import hudson.model.Node;
36 import hudson.model.AbstractCIBase;
37 import hudson.model.AbstractProject;
38 import hudson.model.Action;
39 import hudson.model.AdministrativeMonitor;
40 import hudson.model.AllView;
41 import hudson.model.Api;
42 import hudson.model.Computer;
43 import hudson.model.ComputerSet;
44 import hudson.model.DependencyGraph;
45 import hudson.model.Describable;
46 import hudson.model.Descriptor;
47 import hudson.model.DescriptorByNameOwner;
48 import hudson.model.DirectoryBrowserSupport;
49 import hudson.model.Failure;
50 import hudson.model.Fingerprint;
51 import hudson.model.FingerprintCleanupThread;
52 import hudson.model.FingerprintMap;
53 import hudson.model.FullDuplexHttpChannel;
54 import hudson.model.Hudson;
55 import hudson.model.Item;
56 import hudson.model.ItemGroup;
57 import hudson.model.ItemGroupMixIn;
58 import hudson.model.Items;
59 import hudson.model.JDK;
60 import hudson.model.Job;
61 import hudson.model.JobPropertyDescriptor;
62 import hudson.model.Label;
63 import hudson.model.ListView;
64 import hudson.model.LoadBalancer;
65 import hudson.model.ManagementLink;
66 import hudson.model.NoFingerprintMatch;
67 import hudson.model.OverallLoadStatistics;
68 import hudson.model.Project;
69 import hudson.model.RestartListener;
70 import hudson.model.RootAction;
71 import hudson.model.Slave;
72 import hudson.model.TaskListener;
73 import hudson.model.TopLevelItem;
74 import hudson.model.TopLevelItemDescriptor;
75 import hudson.model.UnprotectedRootAction;
76 import hudson.model.UpdateCenter;
77 import hudson.model.User;
78 import hudson.model.View;
79 import hudson.model.ViewGroup;
80 import hudson.model.ViewGroupMixIn;
81 import hudson.model.Descriptor.FormException;
82 import hudson.model.labels.LabelAtom;
83 import hudson.model.listeners.ItemListener;
84 import hudson.model.listeners.SCMListener;
85 import hudson.model.listeners.SaveableListener;
86 import hudson.model.Queue;
87 import hudson.model.WorkspaceCleanupThread;
88
89 import antlr.ANTLRException;
90 import com.google.common.collect.ImmutableMap;
91 import com.thoughtworks.xstream.XStream;
92 import hudson.BulkChange;
93 import hudson.DNSMultiCast;
94 import hudson.DescriptorExtensionList;
95 import hudson.Extension;
96 import hudson.ExtensionList;
97 import hudson.FilePath;
98 import hudson.Functions;
99 import hudson.Launcher;
100 import hudson.Launcher.LocalLauncher;
101 import hudson.LocalPluginManager;
102 import hudson.Lookup;
103 import hudson.markup.MarkupFormatter;
104 import hudson.Plugin;
105 import hudson.PluginManager;
106 import hudson.PluginWrapper;
107 import hudson.ProxyConfiguration;
108 import hudson.TcpSlaveAgentListener;
109 import hudson.UDPBroadcastThread;
110 import hudson.Util;
111 import static hudson.Util.fixEmpty;
112 import static hudson.Util.fixNull;
113 import hudson.XmlFile;
114 import hudson.cli.CLICommand;
115 import hudson.cli.CliEntryPoint;
116 import hudson.cli.CliManagerImpl;
117 import hudson.cli.declarative.CLIMethod;
118 import hudson.cli.declarative.CLIResolver;
119 import hudson.init.InitMilestone;
120 import hudson.init.InitStrategy;
121 import hudson.lifecycle.Lifecycle;
122 import hudson.logging.LogRecorderManager;
123 import hudson.lifecycle.RestartNotSupportedException;
124 import hudson.markup.RawHtmlMarkupFormatter;
125 import hudson.remoting.Channel;
126 import hudson.remoting.LocalChannel;
127 import hudson.remoting.VirtualChannel;
128 import hudson.scm.RepositoryBrowser;
129 import hudson.scm.SCM;
130 import hudson.search.CollectionSearchIndex;
131 import hudson.search.SearchIndexBuilder;
132 import hudson.search.SearchItem;
133 import hudson.security.ACL;
134 import hudson.security.AccessControlled;
135 import hudson.security.AuthorizationStrategy;
136 import hudson.security.FederatedLoginService;
137 import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
138 import hudson.security.HudsonFilter;
139 import hudson.security.LegacyAuthorizationStrategy;
140 import hudson.security.LegacySecurityRealm;
141 import hudson.security.Permission;
142 import hudson.security.PermissionGroup;
143 import hudson.security.PermissionScope;
144 import hudson.security.SecurityMode;
145 import hudson.security.SecurityRealm;
146 import hudson.security.csrf.CrumbIssuer;
147 import hudson.slaves.Cloud;
148 import hudson.slaves.ComputerListener;
149 import hudson.slaves.DumbSlave;
150 import hudson.slaves.EphemeralNode;
151 import hudson.slaves.NodeDescriptor;
152 import hudson.slaves.NodeList;
153 import hudson.slaves.NodeProperty;
154 import hudson.slaves.NodePropertyDescriptor;
155 import hudson.slaves.NodeProvisioner;
156 import hudson.slaves.OfflineCause;
157 import hudson.slaves.RetentionStrategy;
158 import hudson.tasks.BuildWrapper;
159 import hudson.tasks.Builder;
160 import hudson.tasks.Publisher;
161 import hudson.triggers.SafeTimerTask;
162 import hudson.triggers.Trigger;
163 import hudson.triggers.TriggerDescriptor;
164 import hudson.util.AdministrativeError;
165 import hudson.util.CaseInsensitiveComparator;
166 import hudson.util.ClockDifference;
167 import hudson.util.CopyOnWriteList;
168 import hudson.util.CopyOnWriteMap;
169 import hudson.util.DaemonThreadFactory;
170 import hudson.util.DescribableList;
171 import hudson.util.FormApply;
172 import hudson.util.FormValidation;
173 import hudson.util.Futures;
174 import hudson.util.HudsonIsLoading;
175 import hudson.util.HudsonIsRestarting;
176 import hudson.util.Iterators;
177 import hudson.util.JenkinsReloadFailed;
178 import hudson.util.Memoizer;
179 import hudson.util.MultipartFormDataParser;
180 import hudson.util.RemotingDiagnostics;
181 import hudson.util.RemotingDiagnostics.HeapDump;
182 import hudson.util.StreamTaskListener;
183 import hudson.util.TextFile;
184 import hudson.util.TimeUnit2;
185 import hudson.util.VersionNumber;
186 import hudson.util.XStream2;
187 import hudson.views.DefaultMyViewsTabBar;
188 import hudson.views.DefaultViewsTabBar;
189 import hudson.views.MyViewsTabBar;
190 import hudson.views.ViewsTabBar;
191 import hudson.widgets.Widget;
192 import jenkins.ExtensionComponentSet;
193 import jenkins.ExtensionRefreshException;
194 import jenkins.InitReactorRunner;
195 import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy;
196 import jenkins.security.ConfidentialKey;
197 import jenkins.security.ConfidentialStore;
198 import jenkins.util.io.FileBoolean;
199 import net.sf.json.JSONObject;
200 import org.acegisecurity.AccessDeniedException;
201 import org.acegisecurity.AcegiSecurityException;
202 import org.acegisecurity.Authentication;
203 import org.acegisecurity.GrantedAuthority;
204 import org.acegisecurity.GrantedAuthorityImpl;
205 import org.acegisecurity.context.SecurityContextHolder;
206 import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
207 import org.acegisecurity.ui.AbstractProcessingFilter;
208 import org.apache.commons.jelly.JellyException;
209 import org.apache.commons.jelly.Script;
210 import org.apache.commons.logging.LogFactory;
211 import org.jvnet.hudson.reactor.Executable;
212 import org.jvnet.hudson.reactor.ReactorException;
213 import org.jvnet.hudson.reactor.Task;
214 import org.jvnet.hudson.reactor.TaskBuilder;
215 import org.jvnet.hudson.reactor.TaskGraphBuilder;
216 import org.jvnet.hudson.reactor.Reactor;
217 import org.jvnet.hudson.reactor.TaskGraphBuilder.Handle;
218 import org.kohsuke.accmod.Restricted;
219 import org.kohsuke.accmod.restrictions.NoExternalUse;
220 import org.kohsuke.args4j.Argument;
221 import org.kohsuke.args4j.Option;
222 import org.kohsuke.stapler.Ancestor;
223 import org.kohsuke.stapler.HttpRedirect;
224 import org.kohsuke.stapler.HttpResponse;
225 import org.kohsuke.stapler.HttpResponses;
226 import org.kohsuke.stapler.MetaClass;
227 import org.kohsuke.stapler.QueryParameter;
228 import org.kohsuke.stapler.Stapler;
229 import org.kohsuke.stapler.StaplerFallback;
230 import org.kohsuke.stapler.StaplerProxy;
231 import org.kohsuke.stapler.StaplerRequest;
232 import org.kohsuke.stapler.StaplerResponse;
233 import org.kohsuke.stapler.WebApp;
234 import org.kohsuke.stapler.export.Exported;
235 import org.kohsuke.stapler.export.ExportedBean;
236 import org.kohsuke.stapler.framework.adjunct.AdjunctManager;
237 import org.kohsuke.stapler.interceptor.RequirePOST;
238 import org.kohsuke.stapler.jelly.JellyClassLoaderTearOff;
239 import org.kohsuke.stapler.jelly.JellyRequestDispatcher;
240 import org.xml.sax.InputSource;
241
242 import javax.crypto.SecretKey;
243 import javax.servlet.RequestDispatcher;
244 import javax.servlet.ServletContext;
245 import javax.servlet.ServletException;
246 import javax.servlet.http.Cookie;
247 import javax.servlet.http.HttpServletResponse;
248
249 import static hudson.init.InitMilestone.*;
250 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
251 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
252 import java.io.File;
253 import java.io.FileFilter;
254 import java.io.IOException;
255 import java.io.InputStream;
256 import java.io.PrintWriter;
257 import java.io.StringWriter;
258 import java.net.BindException;
259 import java.net.URL;
260 import java.nio.charset.Charset;
261 import java.security.SecureRandom;
262 import java.text.Collator;
263 import java.util.ArrayList;
264 import java.util.Arrays;
265 import java.util.Collection;
266 import java.util.Collections;
267 import java.util.Comparator;
268 import java.util.HashMap;
269 import java.util.HashSet;
270 import java.util.Iterator;
271 import java.util.List;
272 import java.util.Map;
273 import java.util.Map.Entry;
274 import java.util.Properties;
275 import java.util.Set;
276 import java.util.Stack;
277 import java.util.StringTokenizer;
278 import java.util.Timer;
279 import java.util.TreeSet;
280 import java.util.UUID;
281 import java.util.concurrent.ConcurrentHashMap;
282 import java.util.concurrent.CopyOnWriteArrayList;
283 import java.util.concurrent.ExecutionException;
284 import java.util.concurrent.ExecutorService;
285 import java.util.concurrent.Future;
286 import java.util.concurrent.LinkedBlockingQueue;
287 import java.util.concurrent.ThreadPoolExecutor;
288 import java.util.concurrent.TimeUnit;
289 import java.util.concurrent.TimeoutException;
290 import java.util.logging.Level;
291 import static java.util.logging.Level.SEVERE;
292 import java.util.logging.LogRecord;
293 import java.util.logging.Logger;
294 import java.util.regex.Pattern;
295 import javax.annotation.CheckForNull;
296 import javax.annotation.Nonnull;
297 import javax.annotation.Nullable;
298
299 /**
300 * Root object of the system.
301 *
302 * @author Kohsuke Kawaguchi
303 */
304 @ExportedBean
305 public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGroup, StaplerProxy, StaplerFallback, ViewGroup, AccessControlled, DescriptorByNameOwner, ModelObjectWithContextMenu {
306 private transient final Queue queue;
307
308 /**
309 * Stores various objects scoped to {@link Jenkins}.
310 */
311 public transient final Lookup lookup = new Lookup();
312
313 /**
314 * We update this field to the current version of Hudson whenever we save {@code config.xml}.
315 * This can be used to detect when an upgrade happens from one version to next.
316 *
317 * <p>
318 * Since this field is introduced starting 1.301, "1.0" is used to represent every version
319 * up to 1.300. This value may also include non-standard versions like "1.301-SNAPSHOT" or
320 * "?", etc., so parsing needs to be done with a care.
321 *
322 * @since 1.301
323 */
324 // this field needs to be at the very top so that other components can look at this value even during unmarshalling
325 private String version = "1.0";
326
327 /**
328 * Number of executors of the master node.
329 */
330 private int numExecutors = 2;
331
332 /**
333 * Job allocation strategy.
334 */
335 private Mode mode = Mode.NORMAL;
336
337 /**
338 * False to enable anyone to do anything.
339 * Left as a field so that we can still read old data that uses this flag.
340 *
341 * @see #authorizationStrategy
342 * @see #securityRealm
343 */
344 private Boolean useSecurity;
345
346 /**
347 * Controls how the
348 * <a href="http://en.wikipedia.org/wiki/Authorization">authorization</a>
349 * is handled in Hudson.
350 * <p>
351 * This ultimately controls who has access to what.
352 *
353 * Never null.
354 */
355 private volatile AuthorizationStrategy authorizationStrategy = AuthorizationStrategy.UNSECURED;
356
357 /**
358 * Controls a part of the
359 * <a href="http://en.wikipedia.org/wiki/Authentication">authentication</a>
360 * handling in Hudson.
361 * <p>
362 * Intuitively, this corresponds to the user database.
363 *
364 * See {@link HudsonFilter} for the concrete authentication protocol.
365 *
366 * Never null. Always use {@link #setSecurityRealm(SecurityRealm)} to
367 * update this field.
368 *
369 * @see #getSecurity()
370 * @see #setSecurityRealm(SecurityRealm)
371 */
372 private volatile SecurityRealm securityRealm = SecurityRealm.NO_AUTHENTICATION;
373
374 /**
375 * The project naming strategy defines/restricts the names which can be given to a project/job. e.g. does the name have to follow a naming convention?
376 */
377 private ProjectNamingStrategy projectNamingStrategy = DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
378
379 /**
380 * Root directory for the workspaces.
381 * This value will be variable-expanded as per {@link #expandVariablesForDirectory}.
382 * @see #getWorkspaceFor(TopLevelItem)
383 */
384 private String workspaceDir = "${ITEM_ROOTDIR}/"+WORKSPACE_DIRNAME;
385
386 /**
387 * Root directory for the builds.
388 * This value will be variable-expanded as per {@link #expandVariablesForDirectory}.
389 * @see #getBuildDirFor(Job)
390 */
391 private String buildsDir = "${ITEM_ROOTDIR}/builds";
392
393 /**
394 * Message displayed in the top page.
395 */
396 private String systemMessage;
397
398 private MarkupFormatter markupFormatter;
399
400 /**
401 * Root directory of the system.
402 */
403 public transient final File root;
404
405 /**
406 * Where are we in the initialization?
407 */
408 private transient volatile InitMilestone initLevel = InitMilestone.STARTED;
409
410 /**
411 * All {@link Item}s keyed by their {@link Item#getName() name}s.
412 */
413 /*package*/ transient final Map<String,TopLevelItem> items = new CopyOnWriteMap.Tree<String,TopLevelItem>(CaseInsensitiveComparator.INSTANCE);
414
415 /**
416 * The sole instance.
417 */
418 private static Jenkins theInstance;
419
420 private transient volatile boolean isQuietingDown;
421 private transient volatile boolean terminating;
422
423 private List<JDK> jdks = new ArrayList<JDK>();
424
425 private transient volatile DependencyGraph dependencyGraph;
426
427 /**
428 * Currently active Views tab bar.
429 */
430 private volatile ViewsTabBar viewsTabBar = new DefaultViewsTabBar();
431
432 /**
433 * Currently active My Views tab bar.
434 */
435 private volatile MyViewsTabBar myViewsTabBar = new DefaultMyViewsTabBar();
436
437 /**
438 * All {@link ExtensionList} keyed by their {@link ExtensionList#extensionType}.
439 */
440 private transient final Memoizer<Class,ExtensionList> extensionLists = new Memoizer<Class,ExtensionList>() {
441 public ExtensionList compute(Class key) {
442 return ExtensionList.create(Jenkins.this,key);
443 }
444 };
445
446 /**
447 * All {@link DescriptorExtensionList} keyed by their {@link DescriptorExtensionList#describableType}.
448 */
449 private transient final Memoizer<Class,DescriptorExtensionList> descriptorLists = new Memoizer<Class,DescriptorExtensionList>() {
450 public DescriptorExtensionList compute(Class key) {
451 return DescriptorExtensionList.createDescriptorList(Jenkins.this,key);
452 }
453 };
454
455 /**
456 * {@link Computer}s in this Hudson system. Read-only.
457 */
458 protected transient final Map<Node,Computer> computers = new CopyOnWriteMap.Hash<Node,Computer>();
459
460 /**
461 * Active {@link Cloud}s.
462 */
463 public final Hudson.CloudList clouds = new Hudson.CloudList(this);
464
465 public static class CloudList extends DescribableList<Cloud,Descriptor<Cloud>> {
466 public CloudList(Jenkins h) {
467 super(h);
468 }
469
470 public CloudList() {// needed for XStream deserialization
471 }
472
473 public Cloud getByName(String name) {
474 for (Cloud c : this)
475 if (c.name.equals(name))
476 return c;
477 return null;
478 }
479
480 @Override
481 protected void onModified() throws IOException {
482 super.onModified();
483 Jenkins.getInstance().trimLabels();
484 }
485 }
486
487 /**
488 * Set of installed cluster nodes.
489 * <p>
490 * We use this field with copy-on-write semantics.
491 * This field has mutable list (to keep the serialization look clean),
492 * but it shall never be modified. Only new completely populated slave
493 * list can be set here.
494 * <p>
495 * The field name should be really {@code nodes}, but again the backward compatibility
496 * prevents us from renaming.
497 */
498 protected volatile NodeList slaves;
499
500 /**
501 * Quiet period.
502 *
503 * This is {@link Integer} so that we can initialize it to '5' for upgrading users.
504 */
505 /*package*/ Integer quietPeriod;
506
507 /**
508 * Global default for {@link AbstractProject#getScmCheckoutRetryCount()}
509 */
510 /*package*/ int scmCheckoutRetryCount;
511
512 /**
513 * {@link View}s.
514 */
515 private final CopyOnWriteArrayList<View> views = new CopyOnWriteArrayList<View>();
516
517 /**
518 * Name of the primary view.
519 * <p>
520 * Start with null, so that we can upgrade pre-1.269 data well.
521 * @since 1.269
522 */
523 private volatile String primaryView;
524
525 private transient final ViewGroupMixIn viewGroupMixIn = new ViewGroupMixIn(this) {
526 protected List<View> views() { return views; }
527 protected String primaryView() { return primaryView; }
528 protected void primaryView(String name) { primaryView=name; }
529 };
530
531
532 private transient final FingerprintMap fingerprintMap = new FingerprintMap();
533
534 /**
535 * Loaded plugins.
536 */
537 public transient final PluginManager pluginManager;
538
539 public transient volatile TcpSlaveAgentListener tcpSlaveAgentListener;
540
541 private transient UDPBroadcastThread udpBroadcastThread;
542
543 private transient DNSMultiCast dnsMultiCast;
544
545 /**
546 * List of registered {@link SCMListener}s.
547 */
548 private transient final CopyOnWriteList<SCMListener> scmListeners = new CopyOnWriteList<SCMListener>();
549
550 /**
551 * TCP slave agent port.
552 * 0 for random, -1 to disable.
553 */
554 private int slaveAgentPort =0;
555
556 /**
557 * Whitespace-separated labels assigned to the master as a {@link Node}.
558 */
559 private String label="";
560
561 /**
562 * {@link hudson.security.csrf.CrumbIssuer}
563 */
564 private volatile CrumbIssuer crumbIssuer;
565
566 /**
567 * All labels known to Jenkins. This allows us to reuse the same label instances
568 * as much as possible, even though that's not a strict requirement.
569 */
570 private transient final ConcurrentHashMap<String,Label> labels = new ConcurrentHashMap<String,Label>();
571
572 /**
573 * Load statistics of the entire system.
574 *
575 * This includes every executor and every job in the system.
576 */
577 @Exported
578 public transient final OverallLoadStatistics overallLoad = new OverallLoadStatistics();
579
580 /**
581 * Load statistics of the free roaming jobs and slaves.
582 *
583 * This includes all executors on {@link Mode#NORMAL} nodes and jobs that do not have any assigned nodes.
584 *
585 * @since 1.467
586 */
587 @Exported
588 public transient final LoadStatistics unlabeledLoad = new UnlabeledLoadStatistics();
589
590 /**
591 * {@link NodeProvisioner} that reacts to {@link #unlabeledLoad}.
592 * @since 1.467
593 */
594 public transient final NodeProvisioner unlabeledNodeProvisioner = new NodeProvisioner(null,unlabeledLoad);
595
596 /**
597 * @deprecated as of 1.467
598 * Use {@link #unlabeledNodeProvisioner}.
599 * This was broken because it was tracking all the executors in the system, but it was only tracking
600 * free-roaming jobs in the queue. So {@link Cloud} fails to launch nodes when you have some exclusive
601 * slaves and free-roaming jobs in the queue.
602 */
603 @Restricted(NoExternalUse.class)
604 public transient final NodeProvisioner overallNodeProvisioner = unlabeledNodeProvisioner;
605
606
607 public transient final ServletContext servletContext;
608
609 /**
610 * Transient action list. Useful for adding navigation items to the navigation bar
611 * on the left.
612 */
613 private transient final List<Action> actions = new CopyOnWriteArrayList<Action>();
614
615 /**
616 * List of master node properties
617 */
618 private DescribableList<NodeProperty<?>,NodePropertyDescriptor> nodeProperties = new DescribableList<NodeProperty<?>,NodePropertyDescriptor>(this);
619
620 /**
621 * List of global properties
622 */
623 private DescribableList<NodeProperty<?>,NodePropertyDescriptor> globalNodeProperties = new DescribableList<NodeProperty<?>,NodePropertyDescriptor>(this);
624
625 /**
626 * {@link AdministrativeMonitor}s installed on this system.
627 *
628 * @see AdministrativeMonitor
629 */
630 public transient final List<AdministrativeMonitor> administrativeMonitors = getExtensionList(AdministrativeMonitor.class);
631
632 /**
633 * Widgets on Hudson.
634 */
635 private transient final List<Widget> widgets = getExtensionList(Widget.class);
636
637 /**
638 * {@link AdjunctManager}
639 */
640 private transient final AdjunctManager adjuncts;
641
642 /**
643 * Code that handles {@link ItemGroup} work.
644 */
645 private transient final ItemGroupMixIn itemGroupMixIn = new ItemGroupMixIn(this,this) {
646 @Override
647 protected void add(TopLevelItem item) {
648 items.put(item.getName(),item);
649 }
650
651 @Override
652 protected File getRootDirFor(String name) {
653 return Jenkins.this.getRootDirFor(name);
654 }
655
656 /**
657 * Send the browser to the config page.
658 * use View to trim view/{default-view} from URL if possible
659 */
660 @Override
661 protected String redirectAfterCreateItem(StaplerRequest req, TopLevelItem result) throws IOException {
662 String redirect = result.getUrl()+"configure";
663 List<Ancestor> ancestors = req.getAncestors();
664 for (int i = ancestors.size() - 1; i >= 0; i--) {
665 Object o = ancestors.get(i).getObject();
666 if (o instanceof View) {
667 redirect = req.getContextPath() + '/' + ((View)o).getUrl() + redirect;
668 break;
669 }
670 }
671 return redirect;
672 }
673 };
674
675
676 /**
677 * Hook for a test harness to intercept Jenkins.getInstance()
678 *
679 * Do not use in the production code as the signature may change.
680 */
681 public interface JenkinsHolder {
682 Jenkins getInstance();
683 }
684
685 static JenkinsHolder HOLDER = new JenkinsHolder() {
686 public Jenkins getInstance() {
687 return theInstance;
688 }
689 };
690
691 @CLIResolver
692 public static Jenkins getInstance() {
693 return HOLDER.getInstance();
694 }
695
696 /**
697 * Secret key generated once and used for a long time, beyond
698 * container start/stop. Persisted outside <tt>config.xml</tt> to avoid
699 * accidental exposure.
700 */
701 private transient final String secretKey;
702
703 private transient final UpdateCenter updateCenter = new UpdateCenter();
704
705 /**
706 * True if the user opted out from the statistics tracking. We'll never send anything if this is true.
707 */
708 private Boolean noUsageStatistics;
709
710 /**
711 * HTTP proxy configuration.
712 */
713 public transient volatile ProxyConfiguration proxy;
714
715 /**
716 * Bound to "/log".
717 */
718 private transient final LogRecorderManager log = new LogRecorderManager();
719
720 protected Jenkins(File root, ServletContext context) throws IOException, InterruptedException, ReactorException {
721 this(root,context,null);
722 }
723
724 /**
725 * @param pluginManager
726 * If non-null, use existing plugin manager. create a new one.
727 */
728 @edu.umd.cs.findbugs.annotations.SuppressWarnings({
729 "SC_START_IN_CTOR", // bug in FindBugs. It flags UDPBroadcastThread.start() call but that's for another class
730 "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" // Trigger.timer
731 })
732 protected Jenkins(File root, ServletContext context, PluginManager pluginManager) throws IOException, InterruptedException, ReactorException {
733 long start = System.currentTimeMillis();
734
735 // As Jenkins is starting, grant this process full control
736 ACL.impersonate(ACL.SYSTEM);
737 try {
738 this.root = root;
739 this.servletContext = context;
740 computeVersion(context);
741 if(theInstance!=null)
742 throw new IllegalStateException("second instance");
743 theInstance = this;
744
745 if (!new File(root,"jobs").exists()) {
746 // if this is a fresh install, use more modern default layout that's consistent with slaves
747 workspaceDir = "${JENKINS_HOME}/workspace/${ITEM_FULLNAME}";
748 }
749
750 // doing this early allows InitStrategy to set environment upfront
751 final InitStrategy is = InitStrategy.get(Thread.currentThread().getContextClassLoader());
752
753 Trigger.timer = new Timer("Jenkins cron thread");
754 queue = new Queue(LoadBalancer.CONSISTENT_HASH);
755
756 try {
757 dependencyGraph = DependencyGraph.EMPTY;
758 } catch (InternalError e) {
759 if(e.getMessage().contains("window server")) {
760 throw new Error("Looks like the server runs without X. Please specify -Djava.awt.headless=true as JVM option",e);
761 }
762 throw e;
763 }
764
765 // get or create the secret
766 TextFile secretFile = new TextFile(new File(getRootDir(),"secret.key"));
767 if(secretFile.exists()) {
768 secretKey = secretFile.readTrim();
769 } else {
770 SecureRandom sr = new SecureRandom();
771 byte[] random = new byte[32];
772 sr.nextBytes(random);
773 secretKey = Util.toHexString(random);
774 secretFile.write(secretKey);
775
776 // this marker indicates that the secret.key is generated by the version of Jenkins post SECURITY-49.
777 // this indicates that there's no need to rewrite secrets on disk
778 new FileBoolean(new File(root,"secret.key.not-so-secret")).on();
779 }
780
781 try {
782 proxy = ProxyConfiguration.load();
783 } catch (IOException e) {
784 LOGGER.log(SEVERE, "Failed to load proxy configuration", e);
785 }
786
787 if (pluginManager==null)
788 pluginManager = new LocalPluginManager(this);
789 this.pluginManager = pluginManager;
790 // JSON binding needs to be able to see all the classes from all the plugins
791 WebApp.get(servletContext).setClassLoader(pluginManager.uberClassLoader);
792
793 adjuncts = new AdjunctManager(servletContext, pluginManager.uberClassLoader,"adjuncts/"+SESSION_HASH);
794
795 // initialization consists of ...
796 executeReactor( is,
797 pluginManager.initTasks(is), // loading and preparing plugins
798 loadTasks(), // load jobs
799 InitMilestone.ordering() // forced ordering among key milestones
800 );
801
802 if(KILL_AFTER_LOAD)
803 System.exit(0);
804
805 if(slaveAgentPort!=-1) {
806 try {
807 tcpSlaveAgentListener = new TcpSlaveAgentListener(slaveAgentPort);
808 } catch (BindException e) {
809 new AdministrativeError(getClass().getName()+".tcpBind",
810 "Failed to listen to incoming slave connection",
811 "Failed to listen to incoming slave connection. <a href='configure'>Change the port number</a> to solve the problem.",e);
812 }
813 } else
814 tcpSlaveAgentListener = null;
815
816 try {
817 udpBroadcastThread = new UDPBroadcastThread(this);
818 udpBroadcastThread.start();
819 } catch (IOException e) {
820 LOGGER.log(Level.WARNING, "Failed to broadcast over UDP",e);
821 }
822 dnsMultiCast = new DNSMultiCast(this);
823
824 Timer timer = Trigger.timer;
825 if (timer != null) {
826 timer.scheduleAtFixedRate(new SafeTimerTask() {
827 @Override
828 protected void doRun() throws Exception {
829 trimLabels();
830 }
831 }, TimeUnit2.MINUTES.toMillis(5), TimeUnit2.MINUTES.toMillis(5));
832 }
833
834 updateComputerList();
835
836 {// master is online now
837 Computer c = toComputer();
838 if(c!=null)
839 for (ComputerListener cl : ComputerListener.all())
840 cl.onOnline(c,StreamTaskListener.fromStdout());
841 }
842
843 for (ItemListener l : ItemListener.all()) {
844 long itemListenerStart = System.currentTimeMillis();
845 l.onLoaded();
846 if (LOG_STARTUP_PERFORMANCE)
847 LOGGER.info(String.format("Took %dms for item listener %s startup",
848 System.currentTimeMillis()-itemListenerStart,l.getClass().getName()));
849 }
850
851 if (LOG_STARTUP_PERFORMANCE)
852 LOGGER.info(String.format("Took %dms for complete Jenkins startup",
853 System.currentTimeMillis()-start));
854 } finally {
855 SecurityContextHolder.clearContext();
856 }
857 }
858
859 /**
860 * Executes a reactor.
861 *
862 * @param is
863 * If non-null, this can be consulted for ignoring some tasks. Only used during the initialization of Hudson.
864 */
865 private void executeReactor(final InitStrategy is, TaskBuilder... builders) throws IOException, InterruptedException, ReactorException {
866 Reactor reactor = new Reactor(builders) {
867 /**
868 * Sets the thread name to the task for better diagnostics.
869 */
870 @Override
871 protected void runTask(Task task) throws Exception {
872 if (is!=null && is.skipInitTask(task)) return;
873
874 ACL.impersonate(ACL.SYSTEM); // full access in the initialization thread
875 String taskName = task.getDisplayName();
876
877 Thread t = Thread.currentThread();
878 String name = t.getName();
879 if (taskName !=null)
880 t.setName(taskName);
881 try {
882 long start = System.currentTimeMillis();
883 super.runTask(task);
884 if(LOG_STARTUP_PERFORMANCE)
885 LOGGER.info(String.format("Took %dms for %s by %s",
886 System.currentTimeMillis()-start, taskName, name));
887 } finally {
888 t.setName(name);
889 SecurityContextHolder.clearContext();
890 }
891 }
892 };
893
894 new InitReactorRunner() {
895 @Override
896 protected void onInitMilestoneAttained(InitMilestone milestone) {
897 initLevel = milestone;
898 }
899 }.run(reactor);
900 }
901
902
903 public TcpSlaveAgentListener getTcpSlaveAgentListener() {
904 return tcpSlaveAgentListener;
905 }
906
907 /**
908 * Makes {@link AdjunctManager} URL-bound.
909 * The dummy parameter allows us to use different URLs for the same adjunct,
910 * for proper cache handling.
911 */
912 public AdjunctManager getAdjuncts(String dummy) {
913 return adjuncts;
914 }
915
916 @Exported
917 public int getSlaveAgentPort() {
918 return slaveAgentPort;
919 }
920
921 /**
922 * @param port
923 * 0 to indicate random available TCP port. -1 to disable this service.
924 */
925 public void setSlaveAgentPort(int port) throws IOException {
926 this.slaveAgentPort = port;
927
928 // relaunch the agent
929 if(tcpSlaveAgentListener==null) {
930 if(slaveAgentPort!=-1)
931 tcpSlaveAgentListener = new TcpSlaveAgentListener(slaveAgentPort);
932 } else {
933 if(tcpSlaveAgentListener.configuredPort!=slaveAgentPort) {
934 tcpSlaveAgentListener.shutdown();
935 tcpSlaveAgentListener = null;
936 if(slaveAgentPort!=-1)
937 tcpSlaveAgentListener = new TcpSlaveAgentListener(slaveAgentPort);
938 }
939 }
940 }
941
942 public void setNodeName(String name) {
943 throw new UnsupportedOperationException(); // not allowed
944 }
945
946 public String getNodeDescription() {
947 return Messages.Hudson_NodeDescription();
948 }
949
950 @Exported
951 public String getDescription() {
952 return systemMessage;
953 }
954
955 public PluginManager getPluginManager() {
956 return pluginManager;
957 }
958
959 public UpdateCenter getUpdateCenter() {
960 return updateCenter;
961 }
962
963 public boolean isUsageStatisticsCollected() {
964 return noUsageStatistics==null || !noUsageStatistics;
965 }
966
967 public void setNoUsageStatistics(Boolean noUsageStatistics) throws IOException {
968 this.noUsageStatistics = noUsageStatistics;
969 save();
970 }
971
972 public View.People getPeople() {
973 return new View.People(this);
974 }
975
976 /**
977 * @since 1.484
978 */
979 public View.AsynchPeople getAsynchPeople() {
980 return new View.AsynchPeople(this);
981 }
982
983 /**
984 * Does this {@link View} has any associated user information recorded?
985 * @deprecated Potentially very expensive call; do not use from Jelly views.
986 */
987 public boolean hasPeople() {
988 return View.People.isApplicable(items.values());
989 }
990
991 public Api getApi() {
992 return new Api(this);
993 }
994
995 /**
996 * Returns a secret key that survives across container start/stop.
997 * <p>
998 * This value is useful for implementing some of the security features.
999 *
1000 * @deprecated
1001 * Due to the past security advisory, this value should not be used any more to protect sensitive information.
1002 * See {@link ConfidentialStore} and {@link ConfidentialKey} for how to store secrets.
1003 */
1004 public String getSecretKey() {
1005 return secretKey;
1006 }
1007
1008 /**
1009 * Gets {@linkplain #getSecretKey() the secret key} as a key for AES-128.
1010 * @since 1.308
1011 * @deprecated
1012 * See {@link #getSecretKey()}.
1013 */
1014 public SecretKey getSecretKeyAsAES128() {
1015 return Util.toAes128Key(secretKey);
1016 }
1017
1018 /**
1019 * Returns the unique identifier of this Jenkins that has been historically used to identify
1020 * this Jenkins to the outside world.
1021 *
1022 * <p>
1023 * This form of identifier is weak in that it can be impersonated by others. See
1024 * https://wiki.jenkins-ci.org/display/JENKINS/Instance+Identity for more modern form of instance ID
1025 * that can be challenged and verified.
1026 *
1027 * @since 1.498
1028 */
1029 @SuppressWarnings("deprecation")
1030 public String getLegacyInstanceId() {
1031 return Util.getDigestOf(getSecretKey());
1032 }
1033
1034 /**
1035 * Gets the SCM descriptor by name. Primarily used for making them web-visible.
1036 */
1037 public Descriptor<SCM> getScm(String shortClassName) {
1038 return findDescriptor(shortClassName,SCM.all());
1039 }
1040
1041 /**
1042 * Gets the repository browser descriptor by name. Primarily used for making them web-visible.
1043 */
1044 public Descriptor<RepositoryBrowser<?>> getRepositoryBrowser(String shortClassName) {
1045 return findDescriptor(shortClassName,RepositoryBrowser.all());
1046 }
1047
1048 /**
1049 * Gets the builder descriptor by name. Primarily used for making them web-visible.
1050 */
1051 public Descriptor<Builder> getBuilder(String shortClassName) {
1052 return findDescriptor(shortClassName, Builder.all());
1053 }
1054
1055 /**
1056 * Gets the build wrapper descriptor by name. Primarily used for making them web-visible.
1057 */
1058 public Descriptor<BuildWrapper> getBuildWrapper(String shortClassName) {
1059 return findDescriptor(shortClassName, BuildWrapper.all());
1060 }
1061
1062 /**
1063 * Gets the publisher descriptor by name. Primarily used for making them web-visible.
1064 */
1065 public Descriptor<Publisher> getPublisher(String shortClassName) {
1066 return findDescriptor(shortClassName, Publisher.all());
1067 }
1068
1069 /**
1070 * Gets the trigger descriptor by name. Primarily used for making them web-visible.
1071 */
1072 public TriggerDescriptor getTrigger(String shortClassName) {
1073 return (TriggerDescriptor) findDescriptor(shortClassName, Trigger.all());
1074 }
1075
1076 /**
1077 * Gets the retention strategy descriptor by name. Primarily used for making them web-visible.
1078 */
1079 public Descriptor<RetentionStrategy<?>> getRetentionStrategy(String shortClassName) {
1080 return findDescriptor(shortClassName, RetentionStrategy.all());
1081 }
1082
1083 /**
1084 * Gets the {@link JobPropertyDescriptor} by name. Primarily used for making them web-visible.
1085 */
1086 public JobPropertyDescriptor getJobProperty(String shortClassName) {
1087 // combining these two lines triggers javac bug. See issue #610.
1088 Descriptor d = findDescriptor(shortClassName, JobPropertyDescriptor.all());
1089 return (JobPropertyDescriptor) d;
1090 }
1091
1092 /**
1093 * @deprecated
1094 * UI method. Not meant to be used programatically.
1095 */
1096 public ComputerSet getComputer() {
1097 return new ComputerSet();
1098 }
1099
1100 /**
1101 * Exposes {@link Descriptor} by its name to URL.
1102 *
1103 * After doing all the {@code getXXX(shortClassName)} methods, I finally realized that
1104 * this just doesn't scale.
1105 *
1106 * @param id
1107 * Either {@link Descriptor#getId()} (recommended) or the short name of a {@link Describable} subtype (for compatibility)
1108 * @throws IllegalArgumentException if a short name was passed which matches multiple IDs (fail fast)
1109 */
1110 @SuppressWarnings({"unchecked", "rawtypes"}) // too late to fix
1111 public Descriptor getDescriptor(String id) {
1112 // legacy descriptors that are reigstered manually doesn't show up in getExtensionList, so check them explicitly.
1113 Iterable<Descriptor> descriptors = Iterators.sequence(getExtensionList(Descriptor.class), DescriptorExtensionList.listLegacyInstances());
1114 for (Descriptor d : descriptors) {
1115 if (d.getId().equals(id)) {
1116 return d;
1117 }
1118 }
1119 Descriptor candidate = null;
1120 for (Descriptor d : descriptors) {
1121 String name = d.getId();
1122 if (name.substring(name.lastIndexOf('.') + 1).equals(id)) {
1123 if (candidate == null) {
1124 candidate = d;
1125 } else {
1126 throw new IllegalArgumentException(id + " is ambiguous; matches both " + name + " and " + candidate.getId());
1127 }
1128 }
1129 }
1130 return candidate;
1131 }
1132
1133 /**
1134 * Alias for {@link #getDescriptor(String)}.
1135 */
1136 public Descriptor getDescriptorByName(String id) {
1137 return getDescriptor(id);
1138 }
1139
1140 /**
1141 * Gets the {@link Descriptor} that corresponds to the given {@link Describable} type.
1142 * <p>
1143 * If you have an instance of {@code type} and call {@link Describable#getDescriptor()},
1144 * you'll get the same instance that this method returns.
1145 */
1146 public Descriptor getDescriptor(Class<? extends Describable> type) {
1147 for( Descriptor d : getExtensionList(Descriptor.class) )
1148 if(d.clazz==type)
1149 return d;
1150 return null;
1151 }
1152
1153 /**
1154 * Works just like {@link #getDescriptor(Class)} but don't take no for an answer.
1155 *
1156 * @throws AssertionError
1157 * If the descriptor is missing.
1158 * @since 1.326
1159 */
1160 public Descriptor getDescriptorOrDie(Class<? extends Describable> type) {
1161 Descriptor d = getDescriptor(type);
1162 if (d==null)
1163 throw new AssertionError(type+" is missing its descriptor");
1164 return d;
1165 }
1166
1167 /**
1168 * Gets the {@link Descriptor} instance in the current Hudson by its type.
1169 */
1170 public <T extends Descriptor> T getDescriptorByType(Class<T> type) {
1171 for( Descriptor d : getExtensionList(Descriptor.class) )
1172 if(d.getClass()==type)
1173 return type.cast(d);
1174 return null;
1175 }
1176
1177 /**
1178 * Gets the {@link SecurityRealm} descriptors by name. Primarily used for making them web-visible.
1179 */
1180 public Descriptor<SecurityRealm> getSecurityRealms(String shortClassName) {
1181 return findDescriptor(shortClassName,SecurityRealm.all());
1182 }
1183
1184 /**
1185 * Finds a descriptor that has the specified name.
1186 */
1187 private <T extends Describable<T>>
1188 Descriptor<T> findDescriptor(String shortClassName, Collection<? extends Descriptor<T>> descriptors) {
1189 String name = '.'+shortClassName;
1190 for (Descriptor<T> d : descriptors) {
1191 if(d.clazz.getName().endsWith(name))
1192 return d;
1193 }
1194 return null;
1195 }
1196
1197 protected void updateComputerList() throws IOException {
1198 updateComputerList(AUTOMATIC_SLAVE_LAUNCH);
1199 }
1200
1201 /**
1202 * Gets all the installed {@link SCMListener}s.
1203 */
1204 public CopyOnWriteList<SCMListener> getSCMListeners() {
1205 return scmListeners;
1206 }
1207
1208 /**
1209 * Gets the plugin object from its short name.
1210 *
1211 * <p>
1212 * This allows URL <tt>hudson/plugin/ID</tt> to be served by the views
1213 * of the plugin class.
1214 */
1215 public Plugin getPlugin(String shortName) {
1216 PluginWrapper p = pluginManager.getPlugin(shortName);
1217 if(p==null) return null;
1218 return p.getPlugin();
1219 }
1220
1221 /**
1222 * Gets the plugin object from its class.
1223 *
1224 * <p>
1225 * This allows easy storage of plugin information in the plugin singleton without
1226 * every plugin reimplementing the singleton pattern.
1227 *
1228 * @param clazz The plugin class (beware class-loader fun, this will probably only work
1229 * from within the jpi that defines the plugin class, it may or may not work in other cases)
1230 *
1231 * @return The plugin instance.
1232 */
1233 @SuppressWarnings("unchecked")
1234 public <P extends Plugin> P getPlugin(Class<P> clazz) {
1235 PluginWrapper p = pluginManager.getPlugin(clazz);
1236 if(p==null) return null;
1237 return (P) p.getPlugin();
1238 }
1239
1240 /**
1241 * Gets the plugin objects from their super-class.
1242 *
1243 * @param clazz The plugin class (beware class-loader fun)
1244 *
1245 * @return The plugin instances.
1246 */
1247 public <P extends Plugin> List<P> getPlugins(Class<P> clazz) {
1248 List<P> result = new ArrayList<P>();
1249 for (PluginWrapper w: pluginManager.getPlugins(clazz)) {
1250 result.add((P)w.getPlugin());
1251 }
1252 return Collections.unmodifiableList(result);
1253 }
1254
1255 /**
1256 * Synonym for {@link #getDescription}.
1257 */
1258 public String getSystemMessage() {
1259 return systemMessage;
1260 }
1261
1262 /**
1263 * Gets the markup formatter used in the system.
1264 *
1265 * @return
1266 * never null.
1267 * @since 1.391
1268 */
1269 public MarkupFormatter getMarkupFormatter() {
1270 return markupFormatter!=null ? markupFormatter : RawHtmlMarkupFormatter.INSTANCE;
1271 }
1272
1273 /**
1274 * Sets the markup formatter used in the system globally.
1275 *
1276 * @since 1.391
1277 */
1278 public void setMarkupFormatter(MarkupFormatter f) {
1279 this.markupFormatter = f;
1280 }
1281
1282 /**
1283 * Sets the system message.
1284 */
1285 public void setSystemMessage(String message) throws IOException {
1286 this.systemMessage = message;
1287 save();
1288 }
1289
1290 public FederatedLoginService getFederatedLoginService(String name) {
1291 for (FederatedLoginService fls : FederatedLoginService.all()) {
1292 if (fls.getUrlName().equals(name))
1293 return fls;
1294 }
1295 return null;
1296 }
1297
1298 public List<FederatedLoginService> getFederatedLoginServices() {
1299 return FederatedLoginService.all();
1300 }
1301
1302 public Launcher createLauncher(TaskListener listener) {
1303 return new LocalLauncher(listener).decorateFor(this);
1304 }
1305
1306
1307 public String getFullName() {
1308 return "";
1309 }
1310
1311 public String getFullDisplayName() {
1312 return "";
1313 }
1314
1315 /**
1316 * Returns the transient {@link Action}s associated with the top page.
1317 *
1318 * <p>
1319 * Adding {@link Action} is primarily useful for plugins to contribute
1320 * an item to the navigation bar of the top page. See existing {@link Action}
1321 * implementation for it affects the GUI.
1322 *
1323 * <p>
1324 * To register an {@link Action}, implement {@link RootAction} extension point, or write code like
1325 * {@code Hudson.getInstance().getActions().add(...)}.
1326 *
1327 * @return
1328 * Live list where the changes can be made. Can be empty but never null.
1329 * @since 1.172
1330 */
1331 public List<Action> getActions() {
1332 return actions;
1333 }
1334
1335 /**
1336 * Gets just the immediate children of {@link Jenkins}.
1337 *
1338 * @see #getAllItems(Class)
1339 */
1340 @Exported(name="jobs")
1341 public List<TopLevelItem> getItems() {
1342 if (authorizationStrategy instanceof AuthorizationStrategy.Unsecured ||
1343 authorizationStrategy instanceof FullControlOnceLoggedInAuthorizationStrategy) {
1344 return new ArrayList(items.values());
1345 }
1346
1347 List<TopLevelItem> viewableItems = new ArrayList<TopLevelItem>();
1348 for (TopLevelItem item : items.values()) {
1349 if (item.hasPermission(Item.READ))
1350 viewableItems.add(item);
1351 }
1352
1353 return viewableItems;
1354 }
1355
1356 /**
1357 * Returns the read-only view of all the {@link TopLevelItem}s keyed by their names.
1358 * <p>
1359 * This method is efficient, as it doesn't involve any copying.
1360 *
1361 * @since 1.296
1362 */
1363 public Map<String,TopLevelItem> getItemMap() {
1364 return Collections.unmodifiableMap(items);
1365 }
1366
1367 /**
1368 * Gets just the immediate children of {@link Jenkins} but of the given type.
1369 */
1370 public <T> List<T> getItems(Class<T> type) {
1371 List<T> r = new ArrayList<T>();
1372 for (TopLevelItem i : getItems())
1373 if (type.isInstance(i))
1374 r.add(type.cast(i));
1375 return r;
1376 }
1377
1378 /**
1379 * Gets all the {@link Item}s recursively in the {@link ItemGroup} tree
1380 * and filter them by the given type.
1381 */
1382 public <T extends Item> List<T> getAllItems(Class<T> type) {
1383 List<T> r = new ArrayList<T>();
1384
1385 Stack<ItemGroup> q = new Stack<ItemGroup>();
1386 q.push(this);
1387
1388 while(!q.isEmpty()) {
1389 ItemGroup<?> parent = q.pop();
1390 for (Item i : parent.getItems()) {
1391 if(type.isInstance(i)) {
1392 if (i.hasPermission(Item.READ))
1393 r.add(type.cast(i));
1394 }
1395 if(i instanceof ItemGroup)
1396 q.push((ItemGroup)i);
1397 }
1398 }
1399
1400 return r;
1401 }
1402
1403 /**
1404 * Gets all the items recursively.
1405 *
1406 * @since 1.402
1407 */
1408 public List<Item> getAllItems() {
1409 return getAllItems(Item.class);
1410 }
1411
1412 /**
1413 * Gets a list of simple top-level projects.
1414 * @deprecated This method will ignore Maven and matrix projects, as well as projects inside containers such as folders.
1415 * You may prefer to call {@link #getAllItems(Class)} on {@link AbstractProject},
1416 * perhaps also using {@link Util#createSubList} to consider only {@link TopLevelItem}s.
1417 * (That will also consider the caller's permissions.)
1418 * If you really want to get just {@link Project}s at top level, ignoring permissions,
1419 * you can filter the values from {@link #getItemMap} using {@link Util#createSubList}.
1420 */
1421 @Deprecated
1422 public List<Project> getProjects() {
1423 return Util.createSubList(items.values(),Project.class);
1424 }
1425
1426 /**
1427 * Gets the names of all the {@link Job}s.
1428 */
1429 public Collection<String> getJobNames() {
1430 List<String> names = new ArrayList<String>();
1431 for (Job j : getAllItems(Job.class))
1432 names.add(j.getFullName());
1433 return names;
1434 }
1435
1436 public List<Action> getViewActions() {
1437 return getActions();
1438 }
1439
1440 /**
1441 * Gets the names of all the {@link TopLevelItem}s.
1442 */
1443 public Collection<String> getTopLevelItemNames() {
1444 List<String> names = new ArrayList<String>();
1445 for (TopLevelItem j : items.values())
1446 names.add(j.getName());
1447 return names;
1448 }
1449
1450 public View getView(String name) {
1451 return viewGroupMixIn.getView(name);
1452 }
1453
1454 /**
1455 * Gets the read-only list of all {@link View}s.
1456 */
1457 @Exported
1458 public Collection<View> getViews() {
1459 return viewGroupMixIn.getViews();
1460 }
1461
1462 public void addView(View v) throws IOException {
1463 viewGroupMixIn.addView(v);
1464 }
1465
1466 public boolean canDelete(View view) {
1467 return viewGroupMixIn.canDelete(view);
1468 }
1469
1470 public synchronized void deleteView(View view) throws IOException {
1471 viewGroupMixIn.deleteView(view);
1472 }
1473
1474 public void onViewRenamed(View view, String oldName, String newName) {
1475 viewGroupMixIn.onViewRenamed(view,oldName,newName);
1476 }
1477
1478 /**
1479 * Returns the primary {@link View} that renders the top-page of Hudson.
1480 */
1481 @Exported
1482 public View getPrimaryView() {
1483 return viewGroupMixIn.getPrimaryView();
1484 }
1485
1486 public void setPrimaryView(View v) {
1487 this.primaryView = v.getViewName();
1488 }
1489
1490 public ViewsTabBar getViewsTabBar() {
1491 return viewsTabBar;
1492 }
1493
1494 public void setViewsTabBar(ViewsTabBar viewsTabBar) {
1495 this.viewsTabBar = viewsTabBar;
1496 }
1497
1498 public Jenkins getItemGroup() {
1499 return this;
1500 }
1501
1502 public MyViewsTabBar getMyViewsTabBar() {
1503 return myViewsTabBar;
1504 }
1505
1506 public void setMyViewsTabBar(MyViewsTabBar myViewsTabBar) {
1507 this.myViewsTabBar = myViewsTabBar;
1508 }
1509
1510 /**
1511 * Returns true if the current running Jenkins is upgraded from a version earlier than the specified version.
1512 *
1513 * <p>
1514 * This method continues to return true until the system configuration is saved, at which point
1515 * {@link #version} will be overwritten and Hudson forgets the upgrade history.
1516 *
1517 * <p>
1518 * To handle SNAPSHOTS correctly, pass in "1.N.*" to test if it's upgrading from the version
1519 * equal or younger than N. So say if you implement a feature in 1.301 and you want to check
1520 * if the installation upgraded from pre-1.301, pass in "1.300.*"
1521 *
1522 * @since 1.301
1523 */
1524 public boolean isUpgradedFromBefore(VersionNumber v) {
1525 try {
1526 return new VersionNumber(version).isOlderThan(v);
1527 } catch (IllegalArgumentException e) {
1528 // fail to parse this version number
1529 return false;
1530 }
1531 }
1532
1533 /**
1534 * Gets the read-only list of all {@link Computer}s.
1535 */
1536 public Computer[] getComputers() {
1537 Computer[] r = computers.values().toArray(new Computer[computers.size()]);
1538 Arrays.sort(r,new Comparator<Computer>() {
1539 final Collator collator = Collator.getInstance();
1540 public int compare(Computer lhs, Computer rhs) {
1541 if(lhs.getNode()==Jenkins.this) return -1;
1542 if(rhs.getNode()==Jenkins.this) return 1;
1543 return collator.compare(lhs.getDisplayName(), rhs.getDisplayName());
1544 }
1545 });
1546 return r;
1547 }
1548
1549 @CLIResolver
1550 public Computer getComputer(@Argument(required=true,metaVar="NAME",usage="Node name") String name) {
1551 if(name.equals("(master)"))
1552 name = "";
1553
1554 for (Computer c : computers.values()) {
1555 if(c.getName().equals(name))
1556 return c;
1557 }
1558 return null;
1559 }
1560
1561 /**
1562 * Gets the label that exists on this system by the name.
1563 *
1564 * @return null if name is null.
1565 * @see Label#parseExpression(String) (String)
1566 */
1567 public Label getLabel(String expr) {
1568 if(expr==null) return null;
1569 while(true) {
1570 Label l = labels.get(expr);
1571 if(l!=null)
1572 return l;
1573
1574 // non-existent
1575 try {
1576 labels.putIfAbsent(expr,Label.parseExpression(expr));
1577 } catch (ANTLRException e) {
1578 // laxly accept it as a single label atom for backward compatibility
1579 return getLabelAtom(expr);
1580 }
1581 }
1582 }
1583
1584 /**
1585 * Returns the label atom of the given name.
1586 * @return non-null iff name is non-null
1587 */
1588 public @Nullable LabelAtom getLabelAtom(@CheckForNull String name) {
1589 if (name==null) return null;
1590
1591 while(true) {
1592 Label l = labels.get(name);
1593 if(l!=null)
1594 return (LabelAtom)l;
1595
1596 // non-existent
1597 LabelAtom la = new LabelAtom(name);
1598 if (labels.putIfAbsent(name, la)==null)
1599 la.load();
1600 }
1601 }
1602
1603 /**
1604 * Gets all the active labels in the current system.
1605 */
1606 public Set<Label> getLabels() {
1607 Set<Label> r = new TreeSet<Label>();
1608 for (Label l : labels.values()) {
1609 if(!l.isEmpty())
1610 r.add(l);
1611 }
1612 return r;
1613 }
1614
1615 public Set<LabelAtom> getLabelAtoms() {
1616 Set<LabelAtom> r = new TreeSet<LabelAtom>();
1617 for (Label l : labels.values()) {
1618 if(!l.isEmpty() && l instanceof LabelAtom)
1619 r.add((LabelAtom)l);
1620 }
1621 return r;
1622 }
1623
1624 public Queue getQueue() {
1625 return queue;
1626 }
1627
1628 @Override
1629 public String getDisplayName() {
1630 return Messages.Hudson_DisplayName();
1631 }
1632
1633 public List<JDK> getJDKs() {
1634 if(jdks==null)
1635 jdks = new ArrayList<JDK>();
1636 return jdks;
1637 }
1638
1639 /**
1640 * Gets the JDK installation of the given name, or returns null.
1641 */
1642 public JDK getJDK(String name) {
1643 if(name==null) {
1644 // if only one JDK is configured, "default JDK" should mean that JDK.
1645 List<JDK> jdks = getJDKs();
1646 if(jdks.size()==1) return jdks.get(0);
1647 return null;
1648 }
1649 for (JDK j : getJDKs()) {
1650 if(j.getName().equals(name))
1651 return j;
1652 }
1653 return null;
1654 }
1655
1656
1657
1658 /**
1659 * Gets the slave node of the give name, hooked under this Hudson.
1660 */
1661 public @CheckForNull Node getNode(String name) {
1662 return slaves.getNode(name);
1663 }
1664
1665 /**
1666 * Gets a {@link Cloud} by {@link Cloud#name its name}, or null.
1667 */
1668 public Cloud getCloud(String name) {
1669 return clouds.getByName(name);
1670 }
1671
1672 protected Map<Node,Computer> getComputerMap() {
1673 return computers;
1674 }
1675
1676 /**
1677 * Returns all {@link Node}s in the system, excluding {@link Jenkins} instance itself which
1678 * represents the master.
1679 */
1680 public List<Node> getNodes() {
1681 return slaves;
1682 }
1683
1684 /**
1685 * Adds one more {@link Node} to Hudson.
1686 */
1687 public synchronized void addNode(Node n) throws IOException {
1688 if(n==null) throw new IllegalArgumentException();
1689 ArrayList<Node> nl = new ArrayList<Node>(this.slaves);
1690 if(!nl.contains(n)) // defensive check
1691 nl.add(n);
1692 setNodes(nl);
1693 }
1694
1695 /**
1696 * Removes a {@link Node} from Hudson.
1697 */
1698 public synchronized void removeNode(@Nonnull Node n) throws IOException {
1699 Computer c = n.toComputer();
1700 if (c!=null)
1701 c.disconnect(OfflineCause.create(Messages._Hudson_NodeBeingRemoved()));
1702
1703 ArrayList<Node> nl = new ArrayList<Node>(this.slaves);
1704 nl.remove(n);
1705 setNodes(nl);
1706 }
1707
1708 public void setNodes(List<? extends Node> nodes) throws IOException {
1709 this.slaves = new NodeList(nodes);
1710 updateComputerList();
1711 trimLabels();
1712 save();
1713 }
1714
1715 public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties() {
1716 return nodeProperties;
1717 }
1718
1719 public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getGlobalNodeProperties() {
1720 return globalNodeProperties;
1721 }
1722
1723 /**
1724 * Resets all labels and remove invalid ones.
1725 *
1726 * This should be called when the assumptions behind label cache computation changes,
1727 * but we also call this periodically to self-heal any data out-of-sync issue.
1728 */
1729 private void trimLabels() {
1730 for (Iterator<Label> itr = labels.values().iterator(); itr.hasNext();) {
1731 Label l = itr.next();
1732 resetLabel(l);
1733 if(l.isEmpty())
1734 itr.remove();
1735 }
1736 }
1737
1738 /**
1739 * Binds {@link AdministrativeMonitor}s to URL.
1740 */
1741 public AdministrativeMonitor getAdministrativeMonitor(String id) {
1742 for (AdministrativeMonitor m : administrativeMonitors)
1743 if(m.id.equals(id))
1744 return m;
1745 return null;
1746 }
1747
1748 public NodeDescriptor getDescriptor() {
1749 return DescriptorImpl.INSTANCE;
1750 }
1751
1752 public static final class DescriptorImpl extends NodeDescriptor {
1753 @Extension
1754 public static final DescriptorImpl INSTANCE = new DescriptorImpl();
1755
1756 public String getDisplayName() {
1757 return "";
1758 }
1759
1760 @Override
1761 public boolean isInstantiable() {
1762 return false;
1763 }
1764
1765 public FormValidation doCheckNumExecutors(@QueryParameter String value) {
1766 return FormValidation.validateNonNegativeInteger(value);
1767 }
1768
1769 public FormValidation doCheckRawBuildsDir(@QueryParameter String value) {
1770 if (!value.contains("${")) {
1771 File d = new File(value);
1772 if (!d.isDirectory() && (d.getParentFile() == null || !d.getParentFile().canWrite())) {
1773 return FormValidation.error(value + " does not exist and probably cannot be created");
1774 }
1775 // XXX failure to use either ITEM_* variable might be an error too?
1776 }
1777 return FormValidation.ok(); // XXX assumes it will be OK after substitution, but can we be sure?
1778 }
1779
1780 // to route /descriptor/FQCN/xxx to getDescriptor(FQCN).xxx
1781 public Object getDynamic(String token) {
1782 return Jenkins.getInstance().getDescriptor(token);
1783 }
1784 }
1785
1786 /**
1787 * Gets the system default quiet period.
1788 */
1789 public int getQuietPeriod() {
1790 return quietPeriod!=null ? quietPeriod : 5;
1791 }
1792
1793 /**
1794 * Sets the global quiet period.
1795 *
1796 * @param quietPeriod
1797 * null to the default value.
1798 */
1799 public void setQuietPeriod(Integer quietPeriod) throws IOException {
1800 this.quietPeriod = quietPeriod;
1801 save();
1802 }
1803
1804 /**
1805 * Gets the global SCM check out retry count.
1806 */
1807 public int getScmCheckoutRetryCount() {
1808 return scmCheckoutRetryCount;
1809 }
1810
1811 public void setScmCheckoutRetryCount(int scmCheckoutRetryCount) throws IOException {
1812 this.scmCheckoutRetryCount = scmCheckoutRetryCount;
1813 save();
1814 }
1815
1816 @Override
1817 public String getSearchUrl() {
1818 return "";
1819 }
1820
1821 @Override
1822 public SearchIndexBuilder makeSearchIndex() {
1823 return super.makeSearchIndex()
1824 .add("configure", "config","configure")
1825 .add("manage")
1826 .add("log")
1827 .add(new CollectionSearchIndex<TopLevelItem>() {
1828 protected SearchItem get(String key) { return getItem(key); }
1829 protected Collection<TopLevelItem> all() { return getItems(); }
1830 })
1831 .add(getPrimaryView().makeSearchIndex())
1832 .add(new CollectionSearchIndex() {// for computers
1833 protected Computer get(String key) { return getComputer(key); }
1834 protected Collection<Computer> all() { return computers.values(); }
1835 })
1836 .add(new CollectionSearchIndex() {// for users
1837 protected User get(String key) { return User.get(key,false); }
1838 protected Collection<User> all() { return User.getAll(); }
1839 })
1840 .add(new CollectionSearchIndex() {// for views
1841 protected View get(String key) { return getView(key); }
1842 protected Collection<View> all() { return views; }
1843 });
1844 }
1845
1846 public String getUrlChildPrefix() {
1847 return "job";
1848 }
1849
1850 /**
1851 * Gets the absolute URL of Jenkins,
1852 * such as "http://localhost/jenkins/".
1853 *
1854 * <p>
1855 * This method first tries to use the manually configured value, then
1856 * fall back to {@link StaplerRequest#getRootPath()}.
1857 * It is done in this order so that it can work correctly even in the face
1858 * of a reverse proxy.
1859 *
1860 * @return
1861 * This method returns null if this parameter is not configured by the user.
1862 * The caller must gracefully deal with this situation.
1863 * The returned URL will always have the trailing '/'.
1864 * @since 1.66
1865 * @see Descriptor#getCheckUrl(String)
1866 * @see #getRootUrlFromRequest()
1867 */
1868 public String getRootUrl() {
1869 String url = JenkinsLocationConfiguration.get().getUrl();
1870 if(url!=null) {
1871 if (!url.endsWith("/")) url += '/';
1872 return url;
1873 }
1874 StaplerRequest req = Stapler.getCurrentRequest();
1875 if(req!=null)
1876 return getRootUrlFromRequest();
1877 return null;
1878 }
1879
1880 /**
1881 * Is Jenkins running in HTTPS?
1882 *
1883 * Note that we can't really trust {@link StaplerRequest#isSecure()} because HTTPS might be terminated
1884 * in the reverse proxy.
1885 */
1886 public boolean isRootUrlSecure() {
1887 String url = getRootUrl();
1888 return url!=null && url.startsWith("https");
1889 }
1890
1891 /**
1892 * Gets the absolute URL of Hudson top page, such as "http://localhost/hudson/".
1893 *
1894 * <p>
1895 * Unlike {@link #getRootUrl()}, which uses the manually configured value,
1896 * this one uses the current request to reconstruct the URL. The benefit is
1897 * that this is immune to the configuration mistake (users often fail to set the root URL
1898 * correctly, especially when a migration is involved), but the downside
1899 * is that unless you are processing a request, this method doesn't work.
1900 *
1901 * Please note that this will not work in all cases if Jenkins is running behind a
1902 * reverse proxy (e.g. when user has switched off ProxyPreserveHost, which is
1903 * default setup or the actual url uses https) and you should use getRootUrl if
1904 * you want to be sure you reflect user setup.
1905 * See https://wiki.jenkins-ci.org/display/JENKINS/Running+Jenkins+behind+Apache
1906 *
1907 * @since 1.263
1908 */
1909 public String getRootUrlFromRequest() {
1910 StaplerRequest req = Stapler.getCurrentRequest();
1911 StringBuilder buf = new StringBuilder();
1912 buf.append(req.getScheme()+"://");
1913 buf.append(req.getServerName());
1914 if(req.getServerPort()!=80)
1915 buf.append(':').append(req.getServerPort());
1916 buf.append(req.getContextPath()).append('/');
1917 return buf.toString();
1918 }
1919
1920 public File getRootDir() {
1921 return root;
1922 }
1923
1924 public FilePath getWorkspaceFor(TopLevelItem item) {
1925 return new FilePath(expandVariablesForDirectory(workspaceDir, item));
1926 }
1927
1928 public File getBuildDirFor(Job job) {
1929 return expandVariablesForDirectory(buildsDir, job);
1930 }
1931
1932 private File expandVariablesForDirectory(String base, Item item) {
1933 return new File(Util.replaceMacro(base, ImmutableMap.of(
1934 "JENKINS_HOME", getRootDir().getPath(),
1935 "ITEM_ROOTDIR", item.getRootDir().getPath(),
1936 "ITEM_FULLNAME", item.getFullName(), // legacy, deprecated
1937 "ITEM_FULL_NAME", item.getFullName().replace(':','$')))); // safe, see JENKINS-12251
1938 }
1939
1940 public String getRawWorkspaceDir() {
1941 return workspaceDir;
1942 }
1943
1944 public String getRawBuildsDir() {
1945 return buildsDir;
1946 }
1947
1948 public FilePath getRootPath() {
1949 return new FilePath(getRootDir());
1950 }
1951
1952 @Override
1953 public FilePath createPath(String absolutePath) {
1954 return new FilePath((VirtualChannel)null,absolutePath);
1955 }
1956
1957 public ClockDifference getClockDifference() {
1958 return ClockDifference.ZERO;
1959 }
1960
1961 /**
1962 * For binding {@link LogRecorderManager} to "/log".
1963 * Everything below here is admin-only, so do the check here.
1964 */
1965 public LogRecorderManager getLog() {
1966 checkPermission(ADMINISTER);
1967 return log;
1968 }
1969
1970 /**
1971 * A convenience method to check if there's some security
1972 * restrictions in place.
1973 */
1974 @Exported
1975 public boolean isUseSecurity() {
1976 return securityRealm!=SecurityRealm.NO_AUTHENTICATION || authorizationStrategy!=AuthorizationStrategy.UNSECURED;
1977 }
1978
1979 public boolean isUseProjectNamingStrategy(){
1980 return projectNamingStrategy != DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
1981 }
1982
1983 /**
1984 * If true, all the POST requests to Hudson would have to have crumb in it to protect
1985 * Hudson from CSRF vulnerabilities.
1986 */
1987 @Exported
1988 public boolean isUseCrumbs() {
1989 return crumbIssuer!=null;
1990 }
1991
1992 /**
1993 * Returns the constant that captures the three basic security modes
1994 * in Hudson.
1995 */
1996 public SecurityMode getSecurity() {
1997 // fix the variable so that this code works under concurrent modification to securityRealm.
1998 SecurityRealm realm = securityRealm;
1999
2000 if(realm==SecurityRealm.NO_AUTHENTICATION)
2001 return SecurityMode.UNSECURED;
2002 if(realm instanceof LegacySecurityRealm)
2003 return SecurityMode.LEGACY;
2004 return SecurityMode.SECURED;
2005 }
2006
2007 /**
2008 * @return
2009 * never null.
2010 */
2011 public SecurityRealm getSecurityRealm() {
2012 return securityRealm;
2013 }
2014
2015 public void setSecurityRealm(SecurityRealm securityRealm) {
2016 if(securityRealm==null)
2017 securityRealm= SecurityRealm.NO_AUTHENTICATION;
2018 this.useSecurity = true;
2019 this.securityRealm = securityRealm;
2020 // reset the filters and proxies for the new SecurityRealm
2021 try {
2022 HudsonFilter filter = HudsonFilter.get(servletContext);
2023 if (filter == null) {
2024 // Fix for #3069: This filter is not necessarily initialized before the servlets.
2025 // when HudsonFilter does come back, it'll initialize itself.
2026 LOGGER.fine("HudsonFilter has not yet been initialized: Can't perform security setup for now");
2027 } else {
2028 LOGGER.fine("HudsonFilter has been previously initialized: Setting security up");
2029 filter.reset(securityRealm);
2030 LOGGER.fine("Security is now fully set up");
2031 }
2032 } catch (ServletException e) {
2033 // for binary compatibility, this method cannot throw a checked exception
2034 throw new AcegiSecurityException("Failed to configure filter",e) {};
2035 }
2036 }
2037
2038 public void setAuthorizationStrategy(AuthorizationStrategy a) {
2039 if (a == null)
2040 a = AuthorizationStrategy.UNSECURED;
2041 useSecurity = true;
2042 authorizationStrategy = a;
2043 }
2044
2045 public void disableSecurity() {
2046 useSecurity = null;
2047 setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
2048 authorizationStrategy = AuthorizationStrategy.UNSECURED;
2049 markupFormatter = null;
2050 }
2051
2052 public void setProjectNamingStrategy(ProjectNamingStrategy ns) {
2053 if(ns == null){
2054 ns = DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
2055 }
2056 projectNamingStrategy = ns;
2057 }
2058
2059 public Lifecycle getLifecycle() {
2060 return Lifecycle.get();
2061 }
2062
2063 /**
2064 * Gets the dependency injection container that hosts all the extension implementations and other
2065 * components in Jenkins.
2066 *
2067 * @since 1.GUICE
2068 */
2069 public Injector getInjector() {
2070 return lookup(Injector.class);
2071 }
2072
2073 /**
2074 * Returns {@link ExtensionList} that retains the discovered instances for the given extension type.
2075 *
2076 * @param extensionType
2077 * The base type that represents the extension point. Normally {@link ExtensionPoint} subtype
2078 * but that's not a hard requirement.
2079 * @return
2080 * Can be an empty list but never null.
2081 */
2082 @SuppressWarnings({"unchecked"})
2083 public <T> ExtensionList<T> getExtensionList(Class<T> extensionType) {
2084 return extensionLists.get(extensionType);
2085 }
2086
2087 /**
2088 * Used to bind {@link ExtensionList}s to URLs.
2089 *
2090 * @since 1.349
2091 */
2092 public ExtensionList getExtensionList(String extensionType) throws ClassNotFoundException {
2093 return getExtensionList(pluginManager.uberClassLoader.loadClass(extensionType));
2094 }
2095
2096 /**
2097 * Returns {@link ExtensionList} that retains the discovered {@link Descriptor} instances for the given
2098 * kind of {@link Describable}.
2099 *
2100 * @return
2101 * Can be an empty list but never null.
2102 */
2103 @SuppressWarnings({"unchecked"})
2104 public <T extends Describable<T>,D extends Descriptor<T>> DescriptorExtensionList<T,D> getDescriptorList(Class<T> type) {
2105 return descriptorLists.get(type);
2106 }
2107
2108 /**
2109 * Refresh {@link ExtensionList}s by adding all the newly discovered extensions.
2110 *
2111 * Exposed only for {@link PluginManager#dynamicLoad(File)}.
2112 */
2113 public void refreshExtensions() throws ExtensionRefreshException {
2114 ExtensionList<ExtensionFinder> finders = getExtensionList(ExtensionFinder.class);
2115 for (ExtensionFinder ef : finders) {
2116 if (!ef.isRefreshable())
2117 throw new ExtensionRefreshException(ef+" doesn't support refresh");
2118 }
2119
2120 List<ExtensionComponentSet> fragments = Lists.newArrayList();
2121 for (ExtensionFinder ef : finders) {
2122 fragments.add(ef.refresh());
2123 }
2124 ExtensionComponentSet delta = ExtensionComponentSet.union(fragments).filtered();
2125
2126 // if we find a new ExtensionFinder, we need it to list up all the extension points as well
2127 List<ExtensionComponent<ExtensionFinder>> newFinders = Lists.newArrayList(delta.find(ExtensionFinder.class));
2128 while (!newFinders.isEmpty()) {
2129 ExtensionFinder f = newFinders.remove(newFinders.size()-1).getInstance();
2130
2131 ExtensionComponentSet ecs = ExtensionComponentSet.allOf(f).filtered();
2132 newFinders.addAll(ecs.find(ExtensionFinder.class));
2133 delta = ExtensionComponentSet.union(delta, ecs);
2134 }
2135
2136 for (ExtensionList el : extensionLists.values()) {
2137 el.refresh(delta);
2138 }
2139 for (ExtensionList el : descriptorLists.values()) {
2140 el.refresh(delta);
2141 }
2142
2143 // TODO: we need some generalization here so that extension points can be notified when a refresh happens?
2144 for (ExtensionComponent<RootAction> ea : delta.find(RootAction.class)) {
2145 Action a = ea.getInstance();
2146 if (!actions.contains(a)) actions.add(a);
2147 }
2148 }
2149
2150 /**
2151 * Returns the root {@link ACL}.
2152 *
2153 * @see AuthorizationStrategy#getRootACL()
2154 */
2155 @Override
2156 public ACL getACL() {
2157 return authorizationStrategy.getRootACL();
2158 }
2159
2160 /**
2161 * @return
2162 * never null.
2163 */
2164 public AuthorizationStrategy getAuthorizationStrategy() {
2165 return authorizationStrategy;
2166 }
2167
2168 /**
2169 * The strategy used to check the project names.
2170 * @return never <code>null</code>
2171 */
2172 public ProjectNamingStrategy getProjectNamingStrategy() {
2173 return projectNamingStrategy == null ? ProjectNamingStrategy.DEFAULT_NAMING_STRATEGY : projectNamingStrategy;
2174 }
2175
2176 /**
2177 * Returns true if Hudson is quieting down.
2178 * <p>
2179 * No further jobs will be executed unless it
2180 * can be finished while other current pending builds
2181 * are still in progress.
2182 */
2183 @Exported
2184 public boolean isQuietingDown() {
2185 return isQuietingDown;
2186 }
2187
2188 /**
2189 * Returns true if the container initiated the termination of the web application.
2190 */
2191 public boolean isTerminating() {
2192 return terminating;
2193 }
2194
2195 /**
2196 * Gets the initialization milestone that we've already reached.
2197 *
2198 * @return
2199 * {@link InitMilestone#STARTED} even if the initialization hasn't been started, so that this method
2200 * never returns null.
2201 */
2202 public InitMilestone getInitLevel() {
2203 return initLevel;
2204 }
2205
2206 public void setNumExecutors(int n) throws IOException {
2207 this.numExecutors = n;
2208 save();
2209 }
2210
2211
2212
2213 /**
2214 * {@inheritDoc}.
2215 *
2216 * Note that the look up is case-insensitive.
2217 */
2218 public TopLevelItem getItem(String name) {
2219 if (name==null) return null;
2220 TopLevelItem item = items.get(name);
2221 if (item==null)
2222 return null;
2223 if (!item.hasPermission(Item.READ)) {
2224 if (item.hasPermission(Item.DISCOVER)) {
2225 throw new AccessDeniedException("Please login to access job " + name);
2226 }
2227 return null;
2228 }
2229 return item;
2230 }
2231
2232 /**
2233 * Gets the item by its path name from the given context
2234 *
2235 * <h2>Path Names</h2>
2236 * <p>
2237 * If the name starts from '/', like "/foo/bar/zot", then it's interpreted as absolute.
2238 * Otherwise, the name should be something like "foo/bar" and it's interpreted like
2239 * relative path name in the file system is, against the given context.
2240 *
2241 * @param context
2242 * null is interpreted as {@link Jenkins}. Base 'directory' of the interpretation.
2243 * @since 1.406
2244 */
2245 public Item getItem(String pathName, ItemGroup context) {
2246 if (context==null) context = this;
2247 if (pathName==null) return null;
2248
2249 if (pathName.startsWith("/")) // absolute
2250 return getItemByFullName(pathName);
2251
2252 Object/*Item|ItemGroup*/ ctx = context;
2253
2254 StringTokenizer tokens = new StringTokenizer(pathName,"/");
2255 while (tokens.hasMoreTokens()) {
2256 String s = tokens.nextToken();
2257 if (s.equals("..")) {
2258 if (ctx instanceof Item) {
2259 ctx = ((Item)ctx).getParent();
2260 continue;
2261 }
2262
2263 ctx=null; // can't go up further
2264 break;
2265 }
2266 if (s.equals(".")) {
2267 continue;
2268 }
2269
2270 if (ctx instanceof ItemGroup) {
2271 ItemGroup g = (ItemGroup) ctx;
2272 Item i = g.getItem(s);
2273 if (i==null || !i.hasPermission(Item.READ)) { // XXX consider DISCOVER
2274 ctx=null; // can't go up further
2275 break;
2276 }
2277 ctx=i;
2278 } else {
2279 return null;
2280 }
2281 }
2282
2283 if (ctx instanceof Item)
2284 return (Item)ctx;
2285
2286 // fall back to the classic interpretation
2287 return getItemByFullName(pathName);
2288 }
2289
2290 public final Item getItem(String pathName, Item context) {
2291 return getItem(pathName,context!=null?context.getParent():null);
2292 }
2293
2294 public final <T extends Item> T getItem(String pathName, ItemGroup context, Class<T> type) {
2295 Item r = getItem(pathName, context);
2296 if (type.isInstance(r))
2297 return type.cast(r);
2298 return null;
2299 }
2300
2301 public final <T extends Item> T getItem(String pathName, Item context, Class<T> type) {
2302 return getItem(pathName,context!=null?context.getParent():null,type);
2303 }
2304
2305 public File getRootDirFor(TopLevelItem child) {
2306 return getRootDirFor(child.getName());
2307 }
2308
2309 private File getRootDirFor(String name) {
2310 return new File(new File(getRootDir(),"jobs"), name);
2311 }
2312
2313 /**
2314 * Gets the {@link Item} object by its full name.
2315 * Full names are like path names, where each name of {@link Item} is
2316 * combined by '/'.
2317 *
2318 * @return
2319 * null if either such {@link Item} doesn't exist under the given full name,
2320 * or it exists but it's no an instance of the given type.
2321 */
2322 public @CheckForNull <T extends Item> T getItemByFullName(String fullName, Class<T> type) {
2323 StringTokenizer tokens = new StringTokenizer(fullName,"/");
2324 ItemGroup parent = this;
2325
2326 if(!tokens.hasMoreTokens()) return null; // for example, empty full name.
2327
2328 while(true) {
2329 Item item = parent.getItem(tokens.nextToken());
2330 if(!tokens.hasMoreTokens()) {
2331 if(type.isInstance(item))
2332 return type.cast(item);
2333 else
2334 return null;
2335 }
2336
2337 if(!(item instanceof ItemGroup))
2338 return null; // this item can't have any children
2339
2340 if (!item.hasPermission(Item.READ))
2341 return null; // XXX consider DISCOVER
2342
2343 parent = (ItemGroup) item;
2344 }
2345 }
2346
2347 public @CheckForNull Item getItemByFullName(String fullName) {
2348 return getItemByFullName(fullName,Item.class);
2349 }
2350
2351 /**
2352 * Gets the user of the given name.
2353 *
2354 * @return the user of the given name, if that person exists or the invoker {@link #hasPermission} on {@link #ADMINISTER}; else null
2355 * @see User#get(String,boolean)
2356 */
2357 public @CheckForNull User getUser(String name) {
2358 return User.get(name,hasPermission(ADMINISTER));
2359 }
2360
2361 public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name ) throws IOException {
2362 return createProject(type, name, true);
2363 }
2364
2365 public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify ) throws IOException {
2366 return itemGroupMixIn.createProject(type,name,notify);
2367 }
2368
2369 /**
2370 * Overwrites the existing item by new one.
2371 *
2372 * <p>
2373 * This is a short cut for deleting an existing job and adding a new one.
2374 */
2375 public synchronized void putItem(TopLevelItem item) throws IOException, InterruptedException {
2376 String name = item.getName();
2377 TopLevelItem old = items.get(name);
2378 if (old ==item) return; // noop
2379
2380 checkPermission(Item.CREATE);
2381 if (old!=null)
2382 old.delete();
2383 items.put(name,item);
2384 ItemListener.fireOnCreated(item);
2385 }
2386
2387 /**
2388 * Creates a new job.
2389 *
2390 * <p>
2391 * This version infers the descriptor from the type of the top-level item.
2392 *
2393 * @throws IllegalArgumentException
2394 * if the project of the given name already exists.
2395 */
2396 public synchronized <T extends TopLevelItem> T createProject( Class<T> type, String name ) throws IOException {
2397 return type.cast(createProject((TopLevelItemDescriptor)getDescriptor(type),name));
2398 }
2399
2400 /**
2401 * Called by {@link Job#renameTo(String)} to update relevant data structure.
2402 * assumed to be synchronized on Hudson by the caller.
2403 */
2404 public void onRenamed(TopLevelItem job, String oldName, String newName) throws IOException {
2405 items.remove(oldName);
2406 items.put(newName,job);
2407
2408 for (View v : views)
2409 v.onJobRenamed(job, oldName, newName);
2410 save();
2411 }
2412
2413 /**
2414 * Called in response to {@link Job#doDoDelete(StaplerRequest, StaplerResponse)}
2415 */
2416 public void onDeleted(TopLevelItem item) throws IOException {
2417 for (ItemListener l : ItemListener.all())
2418 l.onDeleted(item);
2419
2420 items.remove(item.getName());
2421 for (View v : views)
2422 v.onJobRenamed(item, item.getName(), null);
2423 save();
2424 }
2425
2426 public FingerprintMap getFingerprintMap() {
2427 return fingerprintMap;
2428 }
2429
2430 // if no finger print matches, display "not found page".
2431 public Object getFingerprint( String md5sum ) throws IOException {
2432 Fingerprint r = fingerprintMap.get(md5sum);
2433 if(r==null) return new NoFingerprintMatch(md5sum);
2434 else return r;
2435 }
2436
2437 /**
2438 * Gets a {@link Fingerprint} object if it exists.
2439 * Otherwise null.
2440 */
2441 public Fingerprint _getFingerprint( String md5sum ) throws IOException {
2442 return fingerprintMap.get(md5sum);
2443 }
2444
2445 /**
2446 * The file we save our configuration.
2447 */
2448 private XmlFile getConfigFile() {
2449 return new XmlFile(XSTREAM, new File(root,"config.xml"));
2450 }
2451
2452 public int getNumExecutors() {
2453 return numExecutors;
2454 }
2455
2456 public Mode getMode() {
2457 return mode;
2458 }
2459
2460 public void setMode(Mode m) throws IOException {
2461 this.mode = m;
2462 save();
2463 }
2464
2465 public String getLabelString() {
2466 return fixNull(label).trim();
2467 }
2468
2469 @Override
2470 public void setLabelString(String label) throws IOException {
2471 this.label = label;
2472 save();
2473 }
2474
2475 @Override
2476 public LabelAtom getSelfLabel() {
2477 return getLabelAtom("master");
2478 }
2479
2480 public Computer createComputer() {
2481 return new Hudson.MasterComputer();
2482 }
2483
2484 private synchronized TaskBuilder loadTasks() throws IOException {
2485 File projectsDir = new File(root,"jobs");
2486 if(!projectsDir.getCanonicalFile().isDirectory() && !projectsDir.mkdirs()) {
2487 if(projectsDir.exists())
2488 throw new IOException(projectsDir+" is not a directory");
2489 throw new IOException("Unable to create "+projectsDir+"\nPermission issue? Please create this directory manually.");
2490 }
2491 File[] subdirs = projectsDir.listFiles(new FileFilter() {
2492 public boolean accept(File child) {
2493 return child.isDirectory() && Items.getConfigFile(child).exists();
2494 }
2495 });
2496
2497 TaskGraphBuilder g = new TaskGraphBuilder();
2498 Handle loadHudson = g.requires(EXTENSIONS_AUGMENTED).attains(JOB_LOADED).add("Loading global config", new Executable() {
2499 public void run(Reactor session) throws Exception {
2500 // JENKINS-8043: some slaves (eg. swarm slaves) are not saved into the config file
2501 // and will get overwritten when reloading. Make a backup copy now, and re-add them later
2502 NodeList oldSlaves = slaves;
2503
2504 XmlFile cfg = getConfigFile();
2505 if (cfg.exists()) {
2506 // reset some data that may not exist in the disk file
2507 // so that we can take a proper compensation action later.
2508 primaryView = null;
2509 views.clear();
2510
2511 // load from disk
2512 cfg.unmarshal(Jenkins.this);
2513 }
2514
2515 // if we are loading old data that doesn't have this field
2516 if (slaves == null) slaves = new NodeList();
2517
2518 clouds.setOwner(Jenkins.this);
2519 items.clear();
2520
2521 // JENKINS-8043: re-add the slaves which were not saved into the config file
2522 // and are now missing, but still connected.
2523 if (oldSlaves != null) {
2524 ArrayList<Node> newSlaves = new ArrayList<Node>(slaves);
2525 for (Node n: oldSlaves) {
2526 if (n instanceof EphemeralNode) {
2527 if(!newSlaves.contains(n)) {
2528 newSlaves.add(n);
2529 }
2530 }
2531 }
2532 setNodes(newSlaves);
2533 }
2534 }
2535 });
2536
2537 for (final File subdir : subdirs) {
2538 g.requires(loadHudson).attains(JOB_LOADED).notFatal().add("Loading job "+subdir.getName(),new Executable() {
2539 public void run(Reactor session) throws Exception {
2540 TopLevelItem item = (TopLevelItem) Items.load(Jenkins.this, subdir);
2541 items.put(item.getName(), item);
2542 }
2543 });
2544 }
2545
2546 g.requires(JOB_LOADED).add("Finalizing set up",new Executable() {
2547 public void run(Reactor session) throws Exception {
2548 rebuildDependencyGraph();
2549
2550 {// recompute label objects - populates the labels mapping.
2551 for (Node slave : slaves)
2552 // Note that not all labels are visible until the slaves have connected.
2553 slave.getAssignedLabels();
2554 getAssignedLabels();
2555 }
2556
2557 // initialize views by inserting the default view if necessary
2558 // this is both for clean Hudson and for backward compatibility.
2559 if(views.size()==0 || primaryView==null) {
2560 View v = new AllView(Messages.Hudson_ViewName());
2561 setViewOwner(v);
2562 views.add(0,v);
2563 primaryView = v.getViewName();
2564 }
2565
2566 // read in old data that doesn't have the security field set
2567 if(authorizationStrategy==null) {
2568 if(useSecurity==null || !useSecurity)
2569 authorizationStrategy = AuthorizationStrategy.UNSECURED;
2570 else
2571 authorizationStrategy = new LegacyAuthorizationStrategy();
2572 }
2573 if(securityRealm==null) {
2574 if(useSecurity==null || !useSecurity)
2575 setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
2576 else
2577 setSecurityRealm(new LegacySecurityRealm());
2578 } else {
2579 // force the set to proxy
2580 setSecurityRealm(securityRealm);
2581 }
2582
2583 if(useSecurity!=null && !useSecurity) {
2584 // forced reset to the unsecure mode.
2585 // this works as an escape hatch for people who locked themselves out.
2586 authorizationStrategy = AuthorizationStrategy.UNSECURED;
2587 setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
2588 }
2589
2590 // Initialize the filter with the crumb issuer
2591 setCrumbIssuer(crumbIssuer);
2592
2593 // auto register root actions
2594 for (Action a : getExtensionList(RootAction.class))
2595 if (!actions.contains(a)) actions.add(a);
2596 }
2597 });
2598
2599 return g;
2600 }
2601
2602 /**
2603 * Save the settings to a file.
2604 */
2605 public synchronized void save() throws IOException {
2606 if(BulkChange.contains(this)) return;
2607 getConfigFile().write(this);
2608 SaveableListener.fireOnChange(this, getConfigFile());
2609 }
2610
2611
2612 /**
2613 * Called to shut down the system.
2614 */
2615 @edu.umd.cs.findbugs.annotations.SuppressWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
2616 public void cleanUp() {
2617 for (ItemListener l : ItemListener.all())
2618 l.onBeforeShutdown();
2619
2620 Set<Future<?>> pending = new HashSet<Future<?>>();
2621 terminating = true;
2622 for( Computer c : computers.values() ) {
2623 c.interrupt();
2624 killComputer(c);
2625 pending.add(c.disconnect(null));
2626 }
2627 if(udpBroadcastThread!=null)
2628 udpBroadcastThread.shutdown();
2629 if(dnsMultiCast!=null)
2630 dnsMultiCast.close();
2631 interruptReloadThread();
2632 Timer timer = Trigger.timer;
2633 if (timer != null) {
2634 timer.cancel();
2635 }
2636 // TODO: how to wait for the completion of the last job?
2637 Trigger.timer = null;
2638 if(tcpSlaveAgentListener!=null)
2639 tcpSlaveAgentListener.shutdown();
2640
2641 if(pluginManager!=null) // be defensive. there could be some ugly timing related issues
2642 pluginManager.stop();
2643
2644 if(getRootDir().exists())
2645 // if we are aborting because we failed to create JENKINS_HOME,
2646 // don't try to save. Issue #536
2647 getQueue().save();
2648
2649 threadPoolForLoad.shutdown();
2650 for (Future<?> f : pending)
2651 try {
2652 f.get(10, TimeUnit.SECONDS); // if clean up operation didn't complete in time, we fail the test
2653 } catch (InterruptedException e) {
2654 Thread.currentThread().interrupt();
2655 break; // someone wants us to die now. quick!
2656 } catch (ExecutionException e) {
2657 LOGGER.log(Level.WARNING, "Failed to shut down properly",e);
2658 } catch (TimeoutException e) {
2659 LOGGER.log(Level.WARNING, "Failed to shut down properly",e);
2660 }
2661
2662 LogFactory.releaseAll();
2663
2664 theInstance = null;
2665 }
2666
2667 public Object getDynamic(String token) {
2668 for (Action a : getActions()) {
2669 String url = a.getUrlName();
2670 if (url==null) continue;
2671 if (url.equals(token) || url.equals('/' + token))
2672 return a;
2673 }
2674 for (Action a : getManagementLinks())
2675 if(a.getUrlName().equals(token))
2676 return a;
2677 return null;
2678 }
2679
2680
2681 //
2682 //
2683 // actions
2684 //
2685 //
2686 /**
2687 * Accepts submission from the configuration page.
2688 */
2689 public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
2690 BulkChange bc = new BulkChange(this);
2691 try {
2692 checkPermission(ADMINISTER);
2693
2694 JSONObject json = req.getSubmittedForm();
2695
2696 workspaceDir = json.getString("rawWorkspaceDir");
2697 buildsDir = json.getString("rawBuildsDir");
2698
2699 systemMessage = Util.nullify(req.getParameter("system_message"));
2700
2701 jdks.clear();
2702 jdks.addAll(req.bindJSONToList(JDK.class,json.get("jdks")));
2703
2704 boolean result = true;
2705 for( Descriptor<?> d : Functions.getSortedDescriptorsForGlobalConfig() )
2706 result &= configureDescriptor(req,json,d);
2707
2708 version = VERSION;
2709
2710 save();
2711 updateComputerList();
2712 if(result)
2713 FormApply.success(req.getContextPath()+'/').generateResponse(req, rsp, null);
2714 else
2715 FormApply.success("configure").generateResponse(req, rsp, null); // back to config
2716 } finally {
2717 bc.commit();
2718 }
2719 }
2720
2721 /**
2722 * Gets the {@link CrumbIssuer} currently in use.
2723 *
2724 * @return null if none is in use.
2725 */
2726 public CrumbIssuer getCrumbIssuer() {
2727 return crumbIssuer;
2728 }
2729
2730 public void setCrumbIssuer(CrumbIssuer issuer) {
2731 crumbIssuer = issuer;
2732 }
2733
2734 public synchronized void doTestPost( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
2735 rsp.sendRedirect("foo");
2736 }
2737
2738 private boolean configureDescriptor(StaplerRequest req, JSONObject json, Descriptor<?> d) throws FormException {
2739 // collapse the structure to remain backward compatible with the JSON structure before 1.
2740 String name = d.getJsonSafeClassName();
2741 JSONObject js = json.has(name) ? json.getJSONObject(name) : new JSONObject(); // if it doesn't have the property, the method returns invalid null object.
2742 json.putAll(js);
2743 return d.configure(req, js);
2744 }
2745
2746 /**
2747 * Accepts submission from the node configuration page.
2748 */
2749 public synchronized void doConfigExecutorsSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
2750 checkPermission(ADMINISTER);
2751
2752 BulkChange bc = new BulkChange(this);
2753 try {
2754 JSONObject json = req.getSubmittedForm();
2755
2756 MasterBuildConfiguration mbc = MasterBuildConfiguration.all().get(MasterBuildConfiguration.class);
2757 if (mbc!=null)
2758 mbc.configure(req,json);
2759
2760 getNodeProperties().rebuild(req, json.optJSONObject("nodeProperties"), NodeProperty.all());
2761 } finally {
2762 bc.commit();
2763 }
2764
2765 rsp.sendRedirect(req.getContextPath()+'/'+toComputer().getUrl()); // back to the computer page
2766 }
2767
2768 /**
2769 * Accepts the new description.
2770 */
2771 public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
2772 getPrimaryView().doSubmitDescription(req, rsp);
2773 }
2774
2775 public synchronized HttpRedirect doQuietDown() throws IOException {
2776 try {
2777 return doQuietDown(false,0);
2778 } catch (InterruptedException e) {
2779 throw new AssertionError(); // impossible
2780 }
2781 }
2782
2783 @CLIMethod(name="quiet-down")
2784 public HttpRedirect doQuietDown(
2785 @Option(name="-block",usage="Block until the system really quiets down and no builds are running") @QueryParameter boolean block,
2786 @Option(name="-timeout",usage="If non-zero, only block up to the specified number of milliseconds") @QueryParameter int timeout) throws InterruptedException, IOException {
2787 synchronized (this) {
2788 checkPermission(ADMINISTER);
2789 isQuietingDown = true;
2790 }
2791 if (block) {
2792 if (timeout > 0) timeout += System.currentTimeMillis();
2793 while (isQuietingDown
2794 && (timeout <= 0 || System.currentTimeMillis() < timeout)
2795 && !RestartListener.isAllReady()) {
2796 Thread.sleep(1000);
2797 }
2798 }
2799 return new HttpRedirect(".");
2800 }
2801
2802 @CLIMethod(name="cancel-quiet-down")
2803 public synchronized HttpRedirect doCancelQuietDown() {
2804 checkPermission(ADMINISTER);
2805 isQuietingDown = false;
2806 getQueue().scheduleMaintenance();
2807 return new HttpRedirect(".");
2808 }
2809
2810 /**
2811 * Backward compatibility. Redirect to the thread dump.
2812 */
2813 public void doClassicThreadDump(StaplerResponse rsp) throws IOException, ServletException {
2814 rsp.sendRedirect2("threadDump");
2815 }
2816
2817 /**
2818 * Obtains the thread dump of all slaves (including the master.)
2819 *
2820 * <p>
2821 * Since this is for diagnostics, it has a built-in precautionary measure against hang slaves.
2822 */
2823 public Map<String,Map<String,String>> getAllThreadDumps() throws IOException, InterruptedException {
2824 checkPermission(ADMINISTER);
2825
2826 // issue the requests all at once
2827 Map<String,Future<Map<String,String>>> future = new HashMap<String, Future<Map<String, String>>>();
2828
2829 for (Computer c : getComputers()) {
2830 try {
2831 future.put(c.getName(), RemotingDiagnostics.getThreadDumpAsync(c.getChannel()));
2832 } catch(Exception e) {
2833 LOGGER.info("Failed to get thread dump for node " + c.getName() + ": " + e.getMessage());
2834 }
2835 }
2836 if (toComputer() == null) {
2837 future.put("master", RemotingDiagnostics.getThreadDumpAsync(MasterComputer.localChannel));
2838 }
2839
2840 // if the result isn't available in 5 sec, ignore that.
2841 // this is a precaution against hang nodes
2842 long endTime = System.currentTimeMillis() + 5000;
2843
2844 Map<String,Map<String,String>> r = new HashMap<String, Map<String, String>>();
2845 for (Entry<String, Future<Map<String, String>>> e : future.entrySet()) {
2846 try {
2847 r.put(e.getKey(), e.getValue().get(endTime-System.currentTimeMillis(), TimeUnit.MILLISECONDS));
2848 } catch (Exception x) {
2849 StringWriter sw = new StringWriter();
2850 x.printStackTrace(new PrintWriter(sw,true));
2851 r.put(e.getKey(), Collections.singletonMap("Failed to retrieve thread dump",sw.toString()));
2852 }
2853 }
2854 return r;
2855 }
2856
2857 public synchronized TopLevelItem doCreateItem( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
2858 return itemGroupMixIn.createTopLevelItem(req, rsp);
2859 }
2860
2861 /**
2862 * @since 1.319
2863 */
2864 public TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException {
2865 return itemGroupMixIn.createProjectFromXML(name, xml);
2866 }
2867
2868
2869 @SuppressWarnings({"unchecked"})
2870 public <T extends TopLevelItem> T copy(T src, String name) throws IOException {
2871 return itemGroupMixIn.copy(src, name);
2872 }
2873
2874 // a little more convenient overloading that assumes the caller gives us the right type
2875 // (or else it will fail with ClassCastException)
2876 public <T extends AbstractProject<?,?>> T copy(T src, String name) throws IOException {
2877 return (T)copy((TopLevelItem)src,name);
2878 }
2879
2880 public synchronized void doCreateView( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
2881 checkPermission(View.CREATE);
2882 addView(View.create(req,rsp, this));
2883 }
2884
2885 /**
2886 * Check if the given name is suitable as a name
2887 * for job, view, etc.
2888 *
2889 * @throws ParseException
2890 * if the given name is not good
2891 */
2892 public static void checkGoodName(String name) throws Failure {
2893 if(name==null || name.length()==0)
2894 throw new Failure(Messages.Hudson_NoName());
2895
2896 for( int i=0; i<name.length(); i++ ) {
2897 char ch = name.charAt(i);
2898 if(Character.isISOControl(ch)) {
2899 throw new Failure(Messages.Hudson_ControlCodeNotAllowed(toPrintableName(name)));
2900 }
2901 if("?*/\\%!@#$^&|<>[]:;".indexOf(ch)!=-1)
2902 throw new Failure(Messages.Hudson_UnsafeChar(ch));
2903 }
2904
2905 // looks good
2906 }
2907
2908 /**
2909 * Makes sure that the given name is good as a job name.
2910 * @return trimmed name if valid; throws ParseException if not
2911 */
2912 private String checkJobName(String name) throws Failure {
2913 checkGoodName(name);
2914 name = name.trim();
2915 projectNamingStrategy.checkName(name);
2916 if(getItem(name)!=null)
2917 throw new Failure(Messages.Hudson_JobAlreadyExists(name));
2918 // looks good
2919 return name;
2920 }
2921
2922 private static String toPrintableName(String name) {
2923 StringBuilder printableName = new StringBuilder();
2924 for( int i=0; i<name.length(); i++ ) {
2925 char ch = name.charAt(i);
2926 if(Character.isISOControl(ch))
2927 printableName.append("\\u").append((int)ch).append(';');
2928 else
2929 printableName.append(ch);
2930 }
2931 return printableName.toString();
2932 }
2933
2934 /**
2935 * Checks if the user was successfully authenticated.
2936 *
2937 * @see BasicAuthenticationFilter
2938 */
2939 public void doSecured( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
2940 if(req.getUserPrincipal()==null) {
2941 // authentication must have failed
2942 rsp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
2943 return;
2944 }
2945
2946 // the user is now authenticated, so send him back to the target
2947 String path = req.getContextPath()+req.getOriginalRestOfPath();
2948 String q = req.getQueryString();
2949 if(q!=null)
2950 path += '?'+q;
2951
2952 rsp.sendRedirect2(path);
2953 }
2954
2955 /**
2956 * Called once the user logs in. Just forward to the top page.
2957 */
2958 public void doLoginEntry( StaplerRequest req, StaplerResponse rsp ) throws IOException {
2959 if(req.getUserPrincipal()==null) {
2960 rsp.sendRedirect2("noPrincipal");
2961 return;
2962 }
2963
2964 String from = req.getParameter("from");
2965 if(from!=null && from.startsWith("/") && !from.equals("/loginError")) {
2966 rsp.sendRedirect2(from); // I'm bit uncomfortable letting users redircted to other sites, make sure the URL falls into this domain
2967 return;
2968 }
2969
2970 String url = AbstractProcessingFilter.obtainFullRequestUrl(req);
2971 if(url!=null) {
2972 // if the login redirect is initiated by Acegi
2973 // this should send the user back to where s/he was from.
2974 rsp.sendRedirect2(url);
2975 return;
2976 }
2977
2978 rsp.sendRedirect2(".");
2979 }
2980
2981 /**
2982 * Logs out the user.
2983 */
2984 public void doLogout( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
2985 securityRealm.doLogout(req, rsp);
2986 }
2987
2988 /**
2989 * Serves jar files for JNLP slave agents.
2990 */
2991 public Slave.JnlpJar getJnlpJars(String fileName) {
2992 return new Slave.JnlpJar(fileName);
2993 }
2994
2995 public Slave.JnlpJar doJnlpJars(StaplerRequest req) {
2996 return new Slave.JnlpJar(req.getRestOfPath().substring(1));
2997 }
2998
2999 /**
3000 * Reloads the configuration.
3001 */
3002 @CLIMethod(name="reload-configuration")
3003 public synchronized HttpResponse doReload() throws IOException {
3004 checkPermission(ADMINISTER);
3005
3006 // engage "loading ..." UI and then run the actual task in a separate thread
3007 servletContext.setAttribute("app", new HudsonIsLoading());
3008
3009 new Thread("Jenkins config reload thread") {
3010 @Override
3011 public void run() {
3012 try {
3013 ACL.impersonate(ACL.SYSTEM);
3014 reload();
3015 } catch (Exception e) {
3016 LOGGER.log(SEVERE,"Failed to reload Jenkins config",e);
3017 WebApp.get(servletContext).setApp(new JenkinsReloadFailed(e));
3018 }
3019 }
3020 }.start();
3021
3022 return HttpResponses.redirectViaContextPath("/");
3023 }
3024
3025 /**
3026 * Reloads the configuration synchronously.
3027 */
3028 public void reload() throws IOException, InterruptedException, ReactorException {
3029 executeReactor(null, loadTasks());
3030 User.reload();
3031 servletContext.setAttribute("app", this);
3032 }
3033
3034 /**
3035 * Do a finger-print check.
3036 */
3037 public void doDoFingerprintCheck( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
3038 // Parse the request
3039 MultipartFormDataParser p = new MultipartFormDataParser(req);
3040 if(isUseCrumbs() && !getCrumbIssuer().validateCrumb(req, p)) {
3041 rsp.sendError(HttpServletResponse.SC_FORBIDDEN,"No crumb found");
3042 }
3043 try {
3044 rsp.sendRedirect2(req.getContextPath()+"/fingerprint/"+
3045 Util.getDigestOf(p.getFileItem("name").getInputStream())+'/');
3046 } finally {
3047 p.cleanUp();
3048 }
3049 }
3050
3051 /**
3052 * For debugging. Expose URL to perform GC.
3053 */
3054 @edu.umd.cs.findbugs.annotations.SuppressWarnings("DM_GC")
3055 public void doGc(StaplerResponse rsp) throws IOException {
3056 checkPermission(Jenkins.ADMINISTER);
3057 System.gc();
3058 rsp.setStatus(HttpServletResponse.SC_OK);
3059 rsp.setContentType("text/plain");
3060 rsp.getWriter().println("GCed");
3061 }
3062
3063 /**
3064 * End point that intentionally throws an exception to test the error behaviour.
3065 */
3066 public void doException() {
3067 throw new RuntimeException();
3068 }
3069
3070 public ContextMenu doContextMenu(StaplerRequest request, StaplerResponse response) throws IOException, JellyException {
3071 ContextMenu menu = new ContextMenu().from(this, request, response);
3072 for (MenuItem i : menu.items) {
3073 if (i.url.equals("/manage")) {
3074 // add "Manage Jenkins" subitems
3075 i.subMenu = new ContextMenu().from(this, request, response, "manage");
3076 }
3077 }
3078 return menu;
3079 }
3080
3081 /**
3082 * Obtains the heap dump.
3083 */
3084 public HeapDump getHeapDump() throws IOException {
3085 return new HeapDump(this,MasterComputer.localChannel);
3086 }
3087
3088 /**
3089 * Simulates OutOfMemoryError.
3090 * Useful to make sure OutOfMemoryHeapDump setting.
3091 */
3092 public void doSimulateOutOfMemory() throws IOException {
3093 checkPermission(ADMINISTER);
3094
3095 System.out.println("Creating artificial OutOfMemoryError situation");
3096 List<Object> args = new ArrayList<Object>();
3097 while (true)
3098 args.add(new byte[1024*1024]);
3099 }
3100
3101 private transient final Map<UUID,FullDuplexHttpChannel> duplexChannels = new HashMap<UUID, FullDuplexHttpChannel>();
3102
3103 /**
3104 * Handles HTTP requests for duplex channels for CLI.
3105 */
3106 public void doCli(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, InterruptedException {
3107 if (!"POST".equals(req.getMethod())) {
3108 // for GET request, serve _cli.jelly, assuming this is a browser
3109 checkPermission(READ);
3110 req.getView(this,"_cli.jelly").forward(req,rsp);
3111 return;
3112 }
3113
3114 // do not require any permission to establish a CLI connection
3115 // the actual authentication for the connecting Channel is done by CLICommand
3116
3117 UUID uuid = UUID.fromString(req.getHeader("Session"));
3118 rsp.setHeader("Hudson-Duplex",""); // set the header so that the client would know
3119
3120 FullDuplexHttpChannel server;
3121 if(req.getHeader("Side").equals("download")) {
3122 duplexChannels.put(uuid,server=new FullDuplexHttpChannel(uuid, !hasPermission(ADMINISTER)) {
3123 protected void main(Channel channel) throws IOException, InterruptedException {
3124 // capture the identity given by the transport, since this can be useful for SecurityRealm.createCliAuthenticator()
3125 channel.setProperty(CLICommand.TRANSPORT_AUTHENTICATION,getAuthentication());
3126 channel.setProperty(CliEntryPoint.class.getName(),new CliManagerImpl(channel));
3127 }
3128 });
3129 try {
3130 server.download(req,rsp);
3131 } finally {
3132 duplexChannels.remove(uuid);
3133 }
3134 } else {
3135 duplexChannels.get(uuid).upload(req,rsp);
3136 }
3137 }
3138
3139 /**
3140 * Binds /userContent/... to $JENKINS_HOME/userContent.
3141 */
3142 public DirectoryBrowserSupport doUserContent() {
3143 return new DirectoryBrowserSupport(this,getRootPath().child("userContent"),"User content","folder.png",true);
3144 }
3145
3146 /**
3147 * Perform a restart of Hudson, if we can.
3148 *
3149 * This first replaces "app" to {@link HudsonIsRestarting}
3150 */
3151 @CLIMethod(name="restart")
3152 public void doRestart(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, RestartNotSupportedException {
3153 checkPermission(ADMINISTER);
3154 if (req != null && req.getMethod().equals("GET")) {
3155 req.getView(this,"_restart.jelly").forward(req,rsp);
3156 return;
3157 }
3158
3159 restart();
3160
3161 if (rsp != null) // null for CLI
3162 rsp.sendRedirect2(".");
3163 }
3164
3165 /**
3166 * Queues up a restart of Hudson for when there are no builds running, if we can.
3167 *
3168 * This first replaces "app" to {@link HudsonIsRestarting}
3169 *
3170 * @since 1.332
3171 */
3172 @CLIMethod(name="safe-restart")
3173 public HttpResponse doSafeRestart(StaplerRequest req) throws IOException, ServletException, RestartNotSupportedException {
3174 checkPermission(ADMINISTER);
3175 if (req != null && req.getMethod().equals("GET"))
3176 return HttpResponses.forwardToView(this,"_safeRestart.jelly");
3177
3178 safeRestart();
3179
3180 return HttpResponses.redirectToDot();
3181 }
3182
3183 /**
3184 * Performs a restart.
3185 */
3186 public void restart() throws RestartNotSupportedException {
3187 final Lifecycle lifecycle = Lifecycle.get();
3188 lifecycle.verifyRestartable(); // verify that Hudson is restartable
3189 servletContext.setAttribute("app", new HudsonIsRestarting());
3190
3191 new Thread("restart thread") {
3192 final String exitUser = getAuthentication().getName();
3193 @Override
3194 public void run() {
3195 try {
3196 ACL.impersonate(ACL.SYSTEM);
3197
3198 // give some time for the browser to load the "reloading" page
3199 Thread.sleep(5000);
3200 LOGGER.severe(String.format("Restarting VM as requested by %s",exitUser));
3201 for (RestartListener listener : RestartListener.all())
3202 listener.onRestart();
3203 lifecycle.restart();
3204 } catch (InterruptedException e) {
3205 LOGGER.log(Level.WARNING, "Failed to restart Hudson",e);
3206 } catch (IOException e) {
3207 LOGGER.log(Level.WARNING, "Failed to restart Hudson",e);
3208 }
3209 }
3210 }.start();
3211 }
3212
3213 /**
3214 * Queues up a restart to be performed once there are no builds currently running.
3215 * @since 1.332
3216 */
3217 public void safeRestart() throws RestartNotSupportedException {
3218 final Lifecycle lifecycle = Lifecycle.get();
3219 lifecycle.verifyRestartable(); // verify that Hudson is restartable
3220 // Quiet down so that we won't launch new builds.
3221 isQuietingDown = true;
3222
3223 new Thread("safe-restart thread") {
3224 final String exitUser = getAuthentication().getName();
3225 @Override
3226 public void run() {
3227 try {
3228 ACL.impersonate(ACL.SYSTEM);
3229
3230 // Wait 'til we have no active executors.
3231 doQuietDown(true, 0);
3232
3233 // Make sure isQuietingDown is still true.
3234 if (isQuietingDown) {
3235 servletContext.setAttribute("app",new HudsonIsRestarting());
3236 // give some time for the browser to load the "reloading" page
3237 LOGGER.info("Restart in 10 seconds");
3238 Thread.sleep(10000);
3239 LOGGER.severe(String.format("Restarting VM as requested by %s",exitUser));
3240 for (RestartListener listener : RestartListener.all())
3241 listener.onRestart();
3242 lifecycle.restart();
3243 } else {
3244 LOGGER.info("Safe-restart mode cancelled");
3245 }
3246 } catch (InterruptedException e) {
3247 LOGGER.log(Level.WARNING, "Failed to restart Hudson",e);
3248 } catch (IOException e) {
3249 LOGGER.log(Level.WARNING, "Failed to restart Hudson",e);
3250 }
3251 }
3252 }.start();
3253 }
3254
3255 /**
3256 * Shutdown the system.
3257 * @since 1.161
3258 */
3259 @CLIMethod(name="shutdown")
3260 public void doExit( StaplerRequest req, StaplerResponse rsp ) throws IOException {
3261 checkPermission(ADMINISTER);
3262 LOGGER.severe(String.format("Shutting down VM as requested by %s from %s",
3263 getAuthentication().getName(), req!=null?req.getRemoteAddr():"???"));
3264 if (rsp!=null) {
3265 rsp.setStatus(HttpServletResponse.SC_OK);
3266 rsp.setContentType("text/plain");
3267 PrintWriter w = rsp.getWriter();
3268 w.println("Shutting down");
3269 w.close();
3270 }
3271
3272 System.exit(0);
3273 }
3274
3275
3276 /**
3277 * Shutdown the system safely.
3278 * @since 1.332
3279 */
3280 @CLIMethod(name="safe-shutdown")
3281 public HttpResponse doSafeExit(StaplerRequest req) throws IOException {
3282 checkPermission(ADMINISTER);
3283 isQuietingDown = true;
3284 final String exitUser = getAuthentication().getName();
3285 final String exitAddr = req!=null ? req.getRemoteAddr() : "unknown";
3286 new Thread("safe-exit thread") {
3287 @Override
3288 public void run() {
3289 try {
3290 ACL.impersonate(ACL.SYSTEM);
3291 LOGGER.severe(String.format("Shutting down VM as requested by %s from %s",
3292 exitUser, exitAddr));
3293 // Wait 'til we have no active executors.
3294 while (isQuietingDown
3295 && (overallLoad.computeTotalExecutors() > overallLoad.computeIdleExecutors())) {
3296 Thread.sleep(5000);
3297 }
3298 // Make sure isQuietingDown is still true.
3299 if (isQuietingDown) {
3300 cleanUp();
3301 System.exit(0);
3302 }
3303 } catch (InterruptedException e) {
3304 LOGGER.log(Level.WARNING, "Failed to shutdown Hudson",e);
3305 }
3306 }
3307 }.start();
3308
3309 return HttpResponses.plainText("Shutting down as soon as all jobs are complete");
3310 }
3311
3312 /**
3313 * Gets the {@link Authentication} object that represents the user
3314 * associated with the current request.
3315 */
3316 public static Authentication getAuthentication() {
3317 Authentication a = SecurityContextHolder.getContext().getAuthentication();
3318 // on Tomcat while serving the login page, this is null despite the fact
3319 // that we have filters. Looking at the stack trace, Tomcat doesn't seem to
3320 // run the request through filters when this is the login request.
3321 // see http://www.nabble.com/Matrix-authorization-problem-tp14602081p14886312.html
3322 if(a==null)
3323 a = ANONYMOUS;
3324 return a;
3325 }
3326
3327 /**
3328 * For system diagnostics.
3329 * Run arbitrary Groovy script.
3330 */
3331 public void doScript(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
3332 doScript(req, rsp, req.getView(this, "_script.jelly"));
3333 }
3334
3335 /**
3336 * Run arbitrary Groovy script and return result as plain text.
3337 */
3338 public void doScriptText(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
3339 doScript(req, rsp, req.getView(this, "_scriptText.jelly"));
3340 }
3341
3342 private void doScript(StaplerRequest req, StaplerResponse rsp, RequestDispatcher view) throws IOException, ServletException {
3343 // ability to run arbitrary script is dangerous
3344 checkPermission(RUN_SCRIPTS);
3345
3346 String text = req.getParameter("script");
3347 if (text != null) {
3348 try {
3349 req.setAttribute("output",
3350 RemotingDiagnostics.executeGroovy(text, MasterComputer.localChannel));
3351 } catch (InterruptedException e) {
3352 throw new ServletException(e);
3353 }
3354 }
3355
3356 view.forward(req, rsp);
3357 }
3358
3359 /**
3360 * Evaluates the Jelly script submitted by the client.
3361 *
3362 * This is useful for system administration as well as unit testing.
3363 */
3364 @RequirePOST
3365 public void doEval(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
3366 checkPermission(ADMINISTER);
3367
3368 try {
3369 MetaClass mc = WebApp.getCurrent().getMetaClass(getClass());
3370 Script script = mc.classLoader.loadTearOff(JellyClassLoaderTearOff.class).createContext().compileScript(new InputSource(req.getReader()));
3371 new JellyRequestDispatcher(this,script).forward(req,rsp);
3372 } catch (JellyException e) {
3373 throw new ServletException(e);
3374 }
3375 }
3376
3377 /**
3378 * Sign up for the user account.
3379 */
3380 public void doSignup( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
3381 req.getView(getSecurityRealm(), "signup.jelly").forward(req, rsp);
3382 }
3383
3384 /**
3385 * Changes the icon size by changing the cookie
3386 */
3387 public void doIconSize( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
3388 String qs = req.getQueryString();
3389 if(qs==null || !ICON_SIZE.matcher(qs).matches())
3390 throw new ServletException();
3391 Cookie cookie = new Cookie("iconSize", qs);
3392 cookie.setMaxAge(/* ~4 mo. */9999999); // #762
3393 rsp.addCookie(cookie);
3394 String ref = req.getHeader("Referer");
3395 if(ref==null) ref=".";
3396 rsp.sendRedirect2(ref);
3397 }
3398
3399 public void doFingerprintCleanup(StaplerResponse rsp) throws IOException {
3400 FingerprintCleanupThread.invoke();
3401 rsp.setStatus(HttpServletResponse.SC_OK);
3402 rsp.setContentType("text/plain");
3403 rsp.getWriter().println("Invoked");
3404 }
3405
3406 public void doWorkspaceCleanup(StaplerResponse rsp) throws IOException {
3407 WorkspaceCleanupThread.invoke();
3408 rsp.setStatus(HttpServletResponse.SC_OK);
3409 rsp.setContentType("text/plain");
3410 rsp.getWriter().println("Invoked");
3411 }
3412
3413 /**
3414 * If the user chose the default JDK, make sure we got 'java' in PATH.
3415 */
3416 public FormValidation doDefaultJDKCheck(StaplerRequest request, @QueryParameter String value) {
3417 if(!value.equals("(Default)"))
3418 // assume the user configured named ones properly in system config ---
3419 // or else system config should have reported form field validation errors.
3420 return FormValidation.ok();
3421
3422 // default JDK selected. Does such java really exist?
3423 if(JDK.isDefaultJDKValid(Jenkins.this))
3424 return FormValidation.ok();
3425 else
3426 return FormValidation.errorWithMarkup(Messages.Hudson_NoJavaInPath(request.getContextPath()));
3427 }
3428
3429 /**
3430 * Makes sure that the given name is good as a job name.
3431 */
3432 public FormValidation doCheckJobName(@QueryParameter String value) {
3433 // this method can be used to check if a file exists anywhere in the file system,
3434 // so it should be protected.
3435 checkPermission(Item.CREATE);
3436
3437 if(fixEmpty(value)==null)
3438 return FormValidation.ok();
3439
3440 try {
3441 checkJobName(value);
3442 return FormValidation.ok();
3443 } catch (Failure e) {
3444 return FormValidation.error(e.getMessage());
3445 }
3446 }
3447
3448 /**
3449 * Checks if a top-level view with the given name exists.
3450 */
3451 public FormValidation doViewExistsCheck(@QueryParameter String value) {
3452 checkPermission(View.CREATE);
3453
3454 String view = fixEmpty(value);
3455 if(view==null) return FormValidation.ok();
3456
3457 if(getView(view)==null)
3458 return FormValidation.ok();
3459 else
3460 return FormValidation.error(Messages.Hudson_ViewAlreadyExists(view));
3461 }
3462
3463 /**
3464 * Serves static resources placed along with Jelly view files.
3465 * <p>
3466 * This method can serve a lot of files, so care needs to be taken
3467 * to make this method secure. It's not clear to me what's the best
3468 * strategy here, though the current implementation is based on
3469 * file extensions.
3470 */
3471 public void doResources(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
3472 String path = req.getRestOfPath();
3473 // cut off the "..." portion of /resources/.../path/to/file
3474 // as this is only used to make path unique (which in turn
3475 // allows us to set a long expiration date
3476 path = path.substring(path.indexOf('/',1)+1);
3477
3478 int idx = path.lastIndexOf('.');
3479 String extension = path.substring(idx+1);
3480 if(ALLOWED_RESOURCE_EXTENSIONS.contains(extension)) {
3481 URL url = pluginManager.uberClassLoader.getResource(path);
3482 if(url!=null) {
3483 long expires = MetaClass.NO_CACHE ? 0 : 365L * 24 * 60 * 60 * 1000; /*1 year*/
3484 rsp.serveFile(req,url,expires);
3485 return;
3486 }
3487 }
3488 rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
3489 }
3490
3491 /**
3492 * Extension list that {@link #doResources(StaplerRequest, StaplerResponse)} can serve.
3493 * This set is mutable to allow plugins to add additional extensions.
3494 */
3495 public static final Set<String> ALLOWED_RESOURCE_EXTENSIONS = new HashSet<String>(Arrays.asList(
3496 "js|css|jpeg|jpg|png|gif|html|htm".split("\\|")
3497 ));
3498
3499 /**
3500 * Checks if container uses UTF-8 to decode URLs. See
3501 * http://wiki.jenkins-ci.org/display/JENKINS/Tomcat#Tomcat-i18n
3502 */
3503 public FormValidation doCheckURIEncoding(StaplerRequest request) throws IOException {
3504 // expected is non-ASCII String
3505 final String expected = "\u57f7\u4e8b";
3506 final String value = fixEmpty(request.getParameter("value"));
3507 if (!expected.equals(value))
3508 return FormValidation.warningWithMarkup(Messages.Hudson_NotUsesUTF8ToDecodeURL());
3509 return FormValidation.ok();
3510 }
3511
3512 /**
3513 * Does not check when system default encoding is "ISO-8859-1".
3514 */
3515 public static boolean isCheckURIEncodingEnabled() {
3516 return !"ISO-8859-1".equalsIgnoreCase(System.getProperty("file.encoding"));
3517 }
3518
3519 /**
3520 * Rebuilds the dependency map.
3521 */
3522 public void rebuildDependencyGraph() {
3523 DependencyGraph graph = new DependencyGraph();
3524 graph.build();
3525 // volatile acts a as a memory barrier here and therefore guarantees
3526 // that graph is fully build, before it's visible to other threads
3527 dependencyGraph = graph;
3528 }
3529
3530 public DependencyGraph getDependencyGraph() {
3531 return dependencyGraph;
3532 }
3533
3534 // for Jelly
3535 public List<ManagementLink> getManagementLinks() {
3536 return ManagementLink.all();
3537 }
3538
3539 /**
3540 * Exposes the current user to <tt>/me</tt> URL.
3541 */
3542 public User getMe() {
3543 User u = User.current();
3544 if (u == null)
3545 throw new AccessDeniedException("/me is not available when not logged in");
3546 return u;
3547 }
3548
3549 /**
3550 * Gets the {@link Widget}s registered on this object.
3551 *
3552 * <p>
3553 * Plugins who wish to contribute boxes on the side panel can add widgets
3554 * by {@code getWidgets().add(new MyWidget())} from {@link Plugin#start()}.
3555 */
3556 public List<Widget> getWidgets() {
3557 return widgets;
3558 }
3559
3560 public Object getTarget() {
3561 try {
3562 checkPermission(READ);
3563 } catch (AccessDeniedException e) {
3564 String rest = Stapler.getCurrentRequest().getRestOfPath();
3565 if(rest.startsWith("/login")
3566 || rest.startsWith("/logout")
3567 || rest.startsWith("/accessDenied")
3568 || rest.startsWith("/adjuncts/")
3569 || rest.startsWith("/signup")
3570 || rest.startsWith("/tcpSlaveAgentListener")
3571 // XXX SlaveComputer.doSlaveAgentJnlp; there should be an annotation to request unprotected access
3572 || rest.matches("/computer/[^/]+/slave-agent[.]jnlp") && "true".equals(Stapler.getCurrentRequest().getParameter("encrypt"))
3573 || rest.startsWith("/cli")
3574 || rest.startsWith("/federatedLoginService/")
3575 || rest.startsWith("/securityRealm"))
3576 return this; // URLs that are always visible without READ permission
3577
3578 for (String name : getUnprotectedRootActions()) {
3579 if (rest.startsWith("/" + name + "/") || rest.equals("/" + name)) {
3580 return this;
3581 }
3582 }
3583
3584 throw e;
3585 }
3586 return this;
3587 }
3588
3589 /**
3590 * Gets a list of unprotected root actions.
3591 * These URL prefixes should be exempted from access control checks by container-managed security.
3592 * Ideally would be synchronized with {@link #getTarget}.
3593 * @return a list of {@linkplain Action#getUrlName URL names}
3594 * @since 1.495
3595 */
3596 public Collection<String> getUnprotectedRootActions() {
3597 Set<String> names = new TreeSet<String>();
3598 names.add("jnlpJars"); // XXX cleaner to refactor doJnlpJars into a URA
3599 // XXX consider caching (expiring cache when actions changes)
3600 for (Action a : getActions()) {
3601 if (a instanceof UnprotectedRootAction) {
3602 names.add(a.getUrlName());
3603 }
3604 }
3605 return names;
3606 }
3607
3608 /**
3609 * Fallback to the primary view.
3610 */
3611 public View getStaplerFallback() {
3612 return getPrimaryView();
3613 }
3614
3615 /**
3616 * This method checks all existing jobs to see if displayName is
3617 * unique. It does not check the displayName against the displayName of the
3618 * job that the user is configuring though to prevent a validation warning
3619 * if the user sets the displayName to what it currently is.
3620 * @param displayName
3621 * @param currentJobName
3622 * @return
3623 */
3624 boolean isDisplayNameUnique(String displayName, String currentJobName) {
3625 Collection<TopLevelItem> itemCollection = items.values();
3626
3627 // if there are a lot of projects, we'll have to store their
3628 // display names in a HashSet or something for a quick check
3629 for(TopLevelItem item : itemCollection) {
3630 if(item.getName().equals(currentJobName)) {
3631 // we won't compare the candidate displayName against the current
3632 // item. This is to prevent an validation warning if the user
3633 // sets the displayName to what the existing display name is
3634 continue;
3635 }
3636 else if(displayName.equals(item.getDisplayName())) {
3637 return false;
3638 }
3639 }
3640
3641 return true;
3642 }
3643
3644 /**
3645 * True if there is no item in Jenkins that has this name
3646 * @param name The name to test
3647 * @param currentJobName The name of the job that the user is configuring
3648 * @return
3649 */
3650 boolean isNameUnique(String name, String currentJobName) {
3651 Item item = getItem(name);
3652
3653 if(null==item) {
3654 // the candidate name didn't return any items so the name is unique
3655 return true;
3656 }
3657 else if(item.getName().equals(currentJobName)) {
3658 // the candidate name returned an item, but the item is the item
3659 // that the user is configuring so this is ok
3660 return true;
3661 }
3662 else {
3663 // the candidate name returned an item, so it is not unique
3664 return false;
3665 }
3666 }
3667
3668 /**
3669 * Checks to see if the candidate displayName collides with any
3670 * existing display names or project names
3671 * @param displayName The display name to test
3672 * @param jobName The name of the job the user is configuring
3673 * @return
3674 */
3675 public FormValidation doCheckDisplayName(@QueryParameter String displayName,
3676 @QueryParameter String jobName) {
3677 displayName = displayName.trim();
3678
3679 if(LOGGER.isLoggable(Level.FINE)) {
3680 LOGGER.log(Level.FINE, "Current job name is " + jobName);
3681 }
3682
3683 if(!isNameUnique(displayName, jobName)) {
3684 return FormValidation.warning(Messages.Jenkins_CheckDisplayName_NameNotUniqueWarning(displayName));
3685 }
3686 else if(!isDisplayNameUnique(displayName, jobName)){
3687 return FormValidation.warning(Messages.Jenkins_CheckDisplayName_DisplayNameNotUniqueWarning(displayName));
3688 }
3689 else {
3690 return FormValidation.ok();
3691 }
3692 }
3693
3694 public static class MasterComputer extends Computer {
3695 protected MasterComputer() {
3696 super(Jenkins.getInstance());
3697 }
3698
3699 /**
3700 * Returns "" to match with {@link Jenkins#getNodeName()}.
3701 */
3702 @Override
3703 public String getName() {
3704 return "";
3705 }
3706
3707 @Override
3708 public boolean isConnecting() {
3709 return false;
3710 }
3711
3712 @Override
3713 public String getDisplayName() {
3714 return Messages.Hudson_Computer_DisplayName();
3715 }
3716
3717 @Override
3718 public String getCaption() {
3719 return Messages.Hudson_Computer_Caption();
3720 }
3721
3722 @Override
3723 public String getUrl() {
3724 return "computer/(master)/";
3725 }
3726
3727 public RetentionStrategy getRetentionStrategy() {
3728 return RetentionStrategy.NOOP;
3729 }
3730
3731 /**
3732 * Report an error.
3733 */
3734 @Override
3735 public HttpResponse doDoDelete() throws IOException {
3736 throw HttpResponses.status(SC_BAD_REQUEST);
3737 }
3738
3739 @Override
3740 public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
3741 Jenkins.getInstance().doConfigExecutorsSubmit(req, rsp);
3742 }
3743
3744 @Override
3745 public boolean hasPermission(Permission permission) {
3746 // no one should be allowed to delete the master.
3747 // this hides the "delete" link from the /computer/(master) page.
3748 if(permission==Computer.DELETE)
3749 return false;
3750 // Configuration of master node requires ADMINISTER permission
3751 return super.hasPermission(permission==Computer.CONFIGURE ? Jenkins.ADMINISTER : permission);
3752 }
3753
3754 @Override
3755 public VirtualChannel getChannel() {
3756 return localChannel;
3757 }
3758
3759 @Override
3760 public Charset getDefaultCharset() {
3761 return Charset.defaultCharset();
3762 }
3763
3764 public List<LogRecord> getLogRecords() throws IOException, InterruptedException {
3765 return logRecords;
3766 }
3767
3768 public void doLaunchSlaveAgent(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
3769 // this computer never returns null from channel, so
3770 // this method shall never be invoked.
3771 rsp.sendError(SC_NOT_FOUND);
3772 }
3773
3774 protected Future<?> _connect(boolean forceReconnect) {
3775 return Futures.precomputed(null);
3776 }
3777
3778 /**
3779 * {@link LocalChannel} instance that can be used to execute programs locally.
3780 */
3781 public static final LocalChannel localChannel = new LocalChannel(threadPoolForRemoting);
3782 }
3783
3784 /**
3785 * Shortcut for {@code Hudson.getInstance().lookup.get(type)}
3786 */
3787 public static <T> T lookup(Class<T> type) {
3788 return Jenkins.getInstance().lookup.get(type);
3789 }
3790
3791 /**
3792 * Live view of recent {@link LogRecord}s produced by Hudson.
3793 */
3794 public static List<LogRecord> logRecords = Collections.emptyList(); // initialized to dummy value to avoid NPE
3795
3796 /**
3797 * Thread-safe reusable {@link XStream}.
3798 */
3799 public static final XStream XSTREAM = new XStream2();
3800
3801 /**
3802 * Alias to {@link #XSTREAM} so that one can access additional methods on {@link XStream2} more easily.
3803 */
3804 public static final XStream2 XSTREAM2 = (XStream2)XSTREAM;
3805
3806 private static final int TWICE_CPU_NUM = Math.max(4, Runtime.getRuntime().availableProcessors() * 2);
3807
3808 /**
3809 * Thread pool used to load configuration in parallel, to improve the start up time.
3810 * <p>
3811 * The idea here is to overlap the CPU and I/O, so we want more threads than CPU numbers.
3812 */
3813 /*package*/ transient final ExecutorService threadPoolForLoad = new ThreadPoolExecutor(
3814 TWICE_CPU_NUM, TWICE_CPU_NUM,
3815 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new DaemonThreadFactory());
3816
3817
3818 private static void computeVersion(ServletContext context) {
3819 // set the version
3820 Properties props = new Properties();
3821 try {
3822 InputStream is = Jenkins.class.getResourceAsStream("jenkins-version.properties");
3823 if(is!=null)
3824 props.load(is);
3825 } catch (IOException e) {
3826 e.printStackTrace(); // if the version properties is missing, that's OK.
3827 }
3828 String ver = props.getProperty("version");
3829 if(ver==null) ver="?";
3830 VERSION = ver;
3831 context.setAttribute("version",ver);
3832
3833 VERSION_HASH = Util.getDigestOf(ver).substring(0, 8);
3834 SESSION_HASH = Util.getDigestOf(ver+System.currentTimeMillis()).substring(0, 8);
3835
3836 if(ver.equals("?") || Boolean.getBoolean("hudson.script.noCache"))
3837 RESOURCE_PATH = "";
3838 else
3839 RESOURCE_PATH = "/static/"+SESSION_HASH;
3840
3841 VIEW_RESOURCE_PATH = "/resources/"+ SESSION_HASH;
3842 }
3843
3844 /**
3845 * Version number of this Hudson.
3846 */
3847 public static String VERSION="?";
3848
3849 /**
3850 * Parses {@link #VERSION} into {@link VersionNumber}, or null if it's not parseable as a version number
3851 * (such as when Hudson is run with "mvn hudson-dev:run")
3852 */
3853 public static VersionNumber getVersion() {
3854 try {
3855 return new VersionNumber(VERSION);
3856 } catch (NumberFormatException e) {
3857 try {
3858 // for non-released version of Hudson, this looks like "1.345 (private-foobar), so try to approximate.
3859 int idx = VERSION.indexOf(' ');
3860 if (idx>0)
3861 return new VersionNumber(VERSION.substring(0,idx));
3862 } catch (NumberFormatException _) {
3863 // fall through
3864 }
3865
3866 // totally unparseable
3867 return null;
3868 } catch (IllegalArgumentException e) {
3869 // totally unparseable
3870 return null;
3871 }
3872 }
3873
3874 /**
3875 * Hash of {@link #VERSION}.
3876 */
3877 public static String VERSION_HASH;
3878
3879 /**
3880 * Unique random token that identifies the current session.
3881 * Used to make {@link #RESOURCE_PATH} unique so that we can set long "Expires" header.
3882 *
3883 * We used to use {@link #VERSION_HASH}, but making this session local allows us to
3884 * reuse the same {@link #RESOURCE_PATH} for static resources in plugins.
3885 */
3886 public static String SESSION_HASH;
3887
3888 /**
3889 * Prefix to static resources like images and javascripts in the war file.
3890 * Either "" or strings like "/static/VERSION", which avoids Hudson to pick up
3891 * stale cache when the user upgrades to a different version.
3892 * <p>
3893 * Value computed in {@link WebAppMain}.
3894 */
3895 public static String RESOURCE_PATH = "";
3896
3897 /**
3898 * Prefix to resources alongside view scripts.
3899 * Strings like "/resources/VERSION", which avoids Hudson to pick up
3900 * stale cache when the user upgrades to a different version.
3901 * <p>
3902 * Value computed in {@link WebAppMain}.
3903 */
3904 public static String VIEW_RESOURCE_PATH = "/resources/TBD";
3905
3906 public static boolean PARALLEL_LOAD = Configuration.getBooleanConfigParameter("parallelLoad", true);
3907 public static boolean KILL_AFTER_LOAD = Configuration.getBooleanConfigParameter("killAfterLoad", false);
3908 /**
3909 * Enabled by default as of 1.337. Will keep it for a while just in case we have some serious problems.
3910 */
3911 public static boolean FLYWEIGHT_SUPPORT = Configuration.getBooleanConfigParameter("flyweightSupport", true);
3912
3913 /**
3914 * Tentative switch to activate the concurrent build behavior.
3915 * When we merge this back to the trunk, this allows us to keep
3916 * this feature hidden for a while until we iron out the kinks.
3917 * @see AbstractProject#isConcurrentBuild()
3918 * @deprecated as of 1.464
3919 * This flag will have no effect.
3920 */
3921 @Restricted(NoExternalUse.class)
3922 public static boolean CONCURRENT_BUILD = true;
3923
3924 /**
3925 * Switch to enable people to use a shorter workspace name.
3926 */
3927 private static final String WORKSPACE_DIRNAME = Configuration.getStringConfigParameter("workspaceDirName", "workspace");
3928
3929 /**
3930 * Automatically try to launch a slave when Jenkins is initialized or a new slave is created.
3931 */
3932 public static boolean AUTOMATIC_SLAVE_LAUNCH = true;
3933
3934 private static final Logger LOGGER = Logger.getLogger(Jenkins.class.getName());
3935
3936 private static final Pattern ICON_SIZE = Pattern.compile("\\d+x\\d+");
3937
3938 public static final PermissionGroup PERMISSIONS = Permission.HUDSON_PERMISSIONS;
3939 public static final Permission ADMINISTER = Permission.HUDSON_ADMINISTER;
3940 public static final Permission READ = new Permission(PERMISSIONS,"Read",Messages._Hudson_ReadPermission_Description(),Permission.READ,PermissionScope.JENKINS);
3941 public static final Permission RUN_SCRIPTS = new Permission(PERMISSIONS, "RunScripts", Messages._Hudson_RunScriptsPermission_Description(),ADMINISTER,PermissionScope.JENKINS);
3942
3943 /**
3944 * {@link Authentication} object that represents the anonymous user.
3945 * Because Acegi creates its own {@link AnonymousAuthenticationToken} instances, the code must not
3946 * expect the singleton semantics. This is just a convenient instance.
3947 *
3948 * @since 1.343
3949 */
3950 public static final Authentication ANONYMOUS = new AnonymousAuthenticationToken(
3951 "anonymous","anonymous",new GrantedAuthority[]{new GrantedAuthorityImpl("anonymous")});
3952
3953 static {
3954 XSTREAM.alias("jenkins",Jenkins.class);
3955 XSTREAM.alias("slave", DumbSlave.class);
3956 XSTREAM.alias("jdk",JDK.class);
3957 // for backward compatibility with <1.75, recognize the tag name "view" as well.
3958 XSTREAM.alias("view", ListView.class);
3959 XSTREAM.alias("listView", ListView.class);
3960 // this seems to be necessary to force registration of converter early enough
3961 Mode.class.getEnumConstants();
3962
3963 // double check that initialization order didn't do any harm
3964 assert PERMISSIONS!=null;
3965 assert ADMINISTER!=null;
3966 }
3967
3968 }
3969