~ubuntu-branches/ubuntu/trusty/jenkins/trusty

« back to all changes in this revision

Viewing changes to .pc/build/0007-remove-findbugs.patch/core/src/main/java/jenkins/model/Jenkins.java

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2013-08-13 12:35:19 UTC
  • mfrom: (1.1.13)
  • Revision ID: package-import@ubuntu.com-20130813123519-tizgfxcr70trl7r0
Tags: 1.509.2+dfsg-1
* New upstream release (Closes: #706725):
  - d/control: Update versioned BD's:
    * jenkins-executable-war >= 1.28.
    * jenkins-instance-identity >= 1.3.
    * libjenkins-remoting-java >= 2.23.
    * libjenkins-winstone-java >= 0.9.10-jenkins-44.
    * libstapler-java >= 1.207.
    * libjenkins-json-java >= 2.4-jenkins-1.
    * libstapler-adjunct-timeline-java >= 1.4.
    * libstapler-adjunct-codemirror-java >= 1.2.
    * libmaven-hpi-plugin-java >= 1.93.
    * libjenkins-xstream-java >= 1.4.4-jenkins-3.
  - d/maven.rules: Map to older version of animal-sniffer-maven-plugin.
  - Add patch for compatibility with guava >= 0.14.
  - Add patch to exclude asm4 dependency via jnr-posix.
  - Fixes the following security vulnerabilities:
    CVE-2013-2034, CVE-2013-2033, CVE-2013-2034, CVE-2013-1808
* d/patches/*: Switch to using git patch-queue for managing patches.
* De-duplicate jars between libjenkins-java and jenkins-external-job-monitor
  (Closes: #701163):
  - d/control: Add dependency between jenkins-external-job-monitor ->
    libjenkins-java.
  - d/rules: 
    Drop installation of jenkins-core in jenkins-external-job-monitor.
  - d/jenkins-external-job-monitor.{links,install}: Link to jenkins-core
    in /usr/share/java instead of included version.
* Wait longer for jenkins to stop during restarts (Closes: #704848):
  - d/jenkins.init: Re-sync init script from upstream codebase.

Show diffs side-by-side

added added

removed removed

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