+ Since 1.598 overrides of Descriptor.getId were not correctly handled by form binding, breaking at least the CloudBees Templates plugin.
+ (issue 26781)
+
+ Reverted in 1.611. Archiving of large artifacts. Tar implementation cannot handle files having a size >8GB.
+ (issue 10629)
+
+ The queue state was not updated between scheduling builds.
+ (issue 27708,
+ issue 27871)
+
+ PeepholePermalink RunListenerImpl oncompleted should be triggered before downstream builds are triggered.
+ (issue 20989)
+
+ NPE when /script used on offline slave.
+ (issue 26751)
+
+ Make periodic workspace cleanup configurable through system properties.
+ (issue 21322)
+
+ Do not offer to restart on /restart and /safeRestart if the configuration does not support it.
+ (issue 27414)
+
+ Polling was skipped while quieting down, resulting in ignored commit notifications. This behavior was changed.
+ (issue 26208)
+
+ Starting this version, native packages are produced from the new repository.
+ File issues related to installers and packages in the packaging component.
+
+ JSONP served with the wrong MIME type and rejected by Chrome.
+ (issue 27607)
+
+ Security file pattern whitelist was broken for some plugins since 1.597.
+ (issue 27055)
+
+ Lock an Executor without creating a Thread
+ (issue 25938)
+
+ Hide flyweight master executor when ≥1 heavyweight executors running as subtasks
+ (issue 26900)
+
+ Way to mark an Executable that should not block isReadyToRestart
+ (issue 22941)
+
+ Refactor the Queue and Nodes to use a consistent locking strategy
+ (issue 27565)
+ Note that this change involved moving slave definitions outside the main config.xml file.
+ If you downgrade after this, your slave settings will be lost.
+
+ Makes the Jenkins is loading screen not block on the extensions loading lock
+ (issue 27563)
+
+ AdjunctManager: exception upon startup
+ (issue 15355)
+
+ Removes race condition rendering the list of executors
+ (issue 27564)
+
+ Tidy up the locks that were causing deadlocks with the once retention strategy in durable tasks
+ (issue 27476)
+
+ Remove any requirement from Jenkins Core to lock on the Queue when rendering the Jenkins UI
+ (issue 27566)
+
+ Added a switch (-Dhudson.model.User.allowNonExistentUserToLogin=true) to let users login even when the record is not found in the backend security realm.
+ (issue 22346)
+
+ Avoid deadlock when using build-monitor-plugin.
+ (issue 27183)
+
+ As security hardening, mark "remember me" cookie as HTTP only
+ (issue 27277)
+
+ Show displayName in build remote API.
+ (issue 26723)
+
+ JENKINS_HOME layout change: builds are now keyed by build numbers and not timestamps.
+ See Wiki for details
+ and downgrade.
+ (issue 24380)
+
+ Do not throw exception on /signup when not possible.
+ (issue 11172)
+
+ Tool installer which downloads and unpacks archives should not fail the build if the tool already exists and the server returns an error code.
+ (issue 26196)
+
- Improved the /cli help screen.
+ Improved the /cli help screen.
(issue 20023)
Polling-triggered jobs get scheduled en-mass on start-up if slaves aren't online yet.
@@ -1355,7 +1735,7 @@ Upcoming changes
issue 18879,
issue 19619)
- Upgrade bundled iplugin versions: ssh-slaves to 1.4, ssh-credentials to 1.5.3 and
+ Upgrade bundled iplugin versions: ssh-slaves to 1.4, ssh-credentials to 1.5.3 and
credentials to 1.8.3
(issue 19945)
@@ -1945,7 +2325,7 @@ Upcoming changes
”My Views" links leads to 404 Not Found.
(issue 17317)
- Quoting Issue with JDK Installer with Windows Installer.
+ Quoting Issue with JDK Installer with Windows Installer.
(issue 5408)
Restored compatibility in ArtifactArchiver signature; broken in 1.509 and could affect plugins.
@@ -2021,7 +2401,7 @@ Upcoming changes
ChangeLog should produce some output even if some (plugin) annotator fails
(issue 17084)
- View name should not allow "..".
+ View name should not allow "..".
(issue 16608)
diff --git a/cli/pom.xml b/cli/pom.xml
index 4b40627511..34798c2de9 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -3,14 +3,15 @@
4.0.0
- pomorg.jenkins-ci.main
- 1.595-SNAPSHOT
+ pom
+ 1.616-SNAPSHOTcli
- Jenkins CLI
+ Jenkins cli
+ Command line interface for Jenkins
@@ -41,7 +42,7 @@
org.jvnet.localizerlocalizer
- 1.10
+ 1.23org.jenkins-ci
@@ -93,5 +94,4 @@
-
diff --git a/cli/src/main/java/hudson/cli/CLI.java b/cli/src/main/java/hudson/cli/CLI.java
index 6a3a05c287..de2d2bd795 100644
--- a/cli/src/main/java/hudson/cli/CLI.java
+++ b/cli/src/main/java/hudson/cli/CLI.java
@@ -97,6 +97,7 @@ public class CLI {
* @deprecated
* Use {@link CLIConnectionFactory} to create {@link CLI}
*/
+ @Deprecated
public CLI(URL jenkins, ExecutorService exec) throws IOException, InterruptedException {
this(jenkins,exec,null);
}
@@ -105,6 +106,7 @@ public class CLI {
* @deprecated
* Use {@link CLIConnectionFactory} to create {@link CLI}
*/
+ @Deprecated
public CLI(URL jenkins, ExecutorService exec, String httpsProxyTunnel) throws IOException, InterruptedException {
this(new CLIConnectionFactory().url(jenkins).executorService(exec).httpsProxyTunnel(httpsProxyTunnel));
}
@@ -169,12 +171,18 @@ public class CLI {
private Channel connectViaCliPort(URL jenkins, CliPort clip) throws IOException {
LOGGER.log(FINE, "Trying to connect directly via TCP/IP to {0}", clip.endpoint);
- final Socket s;
+ final Socket s = new Socket();
+ // this prevents a connection from silently terminated by the router in between or the other peer
+ // and that goes without unnoticed. However, the time out is often very long (for example 2 hours
+ // by default in Linux) that this alone is enough to prevent that.
+ s.setKeepAlive(true);
+ // we take care of buffering on our own
+ s.setTcpNoDelay(true);
OutputStream out;
if (httpsProxyTunnel!=null) {
String[] tokens = httpsProxyTunnel.split(":");
- s = new Socket(tokens[0], Integer.parseInt(tokens[1]));
+ s.connect(new InetSocketAddress(tokens[0], Integer.parseInt(tokens[1])));
PrintStream o = new PrintStream(s.getOutputStream());
o.print("CONNECT " + clip.endpoint.getHostName() + ":" + clip.endpoint.getPort() + " HTTP/1.0\r\n\r\n");
@@ -199,7 +207,6 @@ public class CLI {
}
};
} else {
- s = new Socket();
s.connect(clip.endpoint,3000);
out = SocketChannelStream.out(s);
}
@@ -379,7 +386,13 @@ public class CLI {
// h.setLevel(ALL);
// l.addHandler(h);
//
- System.exit(_main(_args));
+ try {
+ System.exit(_main(_args));
+ } catch (Throwable t) {
+ // if the CLI main thread die, make sure to kill the JVM.
+ t.printStackTrace();
+ System.exit(-1);
+ }
}
public static int _main(String[] _args) throws Exception {
diff --git a/cli/src/test/java/hudson/cli/ConnectionTest.java b/cli/src/test/java/hudson/cli/ConnectionTest.java
index 167198a182..8cfac14fb4 100644
--- a/cli/src/test/java/hudson/cli/ConnectionTest.java
+++ b/cli/src/test/java/hudson/cli/ConnectionTest.java
@@ -1,8 +1,9 @@
package hudson.cli;
+import static org.junit.Assert.assertEquals;
+
import hudson.remoting.FastPipedInputStream;
import hudson.remoting.FastPipedOutputStream;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -13,7 +14,8 @@ import java.io.IOException;
/**
* @author Kohsuke Kawaguchi
*/
-public class ConnectionTest extends Assert {
+public class ConnectionTest {
+
Throwable e;
private Connection c1;
private Connection c2;
diff --git a/core/pom.xml b/core/pom.xml
index 87c29d78a7..66c11616d7 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -1,3 +1,4 @@
+
-
4.0.0org.jenkins-ci.mainpom
- 1.595-SNAPSHOT
- ../pom.xml
+ 1.616-SNAPSHOTjenkins-coreJenkins core
-
- Contains the core Jenkins code and view files to render HTML.
-
+ Jenkins core code and view files to render HTML.true
- 1.233
+ 1.2372.5.6.SEC031.8.9
@@ -51,7 +48,7 @@ THE SOFTWARE.
org.jenkins-ci.plugins.icon-shimicon-set
- 1.0.3
+ 1.0.5${project.groupId}
@@ -96,7 +93,6 @@ THE SOFTWARE.
com.google.injectguice
- no_aop
@@ -194,7 +190,7 @@ THE SOFTWARE.
args4jargs4j
- 2.0.23
+ 2.0.31org.jenkins-ci
@@ -214,7 +210,7 @@ THE SOFTWARE.
org.jvnet.localizerlocalizer
- 1.10
+ 1.23antlr
@@ -753,7 +749,7 @@ THE SOFTWARE.
0.5Ctrue
- -XX:MaxPermSize=128m
+ -XX:MaxPermSize=128m -noverify
@@ -862,7 +858,7 @@ THE SOFTWARE.
-
+
findbugs
@@ -880,9 +876,7 @@ THE SOFTWARE.
-
+
cobertura
diff --git a/core/src/main/grammar/crontab.g b/core/src/main/grammar/crontab.g
index f52aaf571c..4ace2d3adf 100644
--- a/core/src/main/grammar/crontab.g
+++ b/core/src/main/grammar/crontab.g
@@ -110,7 +110,7 @@ throws ANTLRException
}
| ("H" "(")=> "H" "(" s=token "-" e=token ")" ( "/" d=token )?
{
- bits = doHash(s,e,d);
+ bits = doHash(s,e,d,field);
}
| "H" ( "/" d=token )?
{
diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java
index 75d1414acc..727bdd7fbd 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -56,6 +56,8 @@ import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
@@ -64,6 +66,7 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.Vector;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
@@ -147,8 +150,8 @@ public class ClassicPluginStrategy implements PluginStrategy {
@Override public PluginWrapper createPluginWrapper(File archive) throws IOException {
final Manifest manifest;
- URL baseResourceURL;
+ URL baseResourceURL = null;
File expandDir = null;
// if .hpi, this is the directory where war is expanded
@@ -197,7 +200,21 @@ public class ClassicPluginStrategy implements PluginStrategy {
if (libs != null)
paths.addAll(Arrays.asList(libs));
- baseResourceURL = expandDir.toURI().toURL();
+ try {
+ Class pathJDK7 = Class.forName("java.nio.file.Path");
+ Object toPath = File.class.getMethod("toPath").invoke(expandDir);
+ URI uri = (URI) pathJDK7.getMethod("toUri").invoke(toPath);
+
+ baseResourceURL = uri.toURL();
+ } catch (NoSuchMethodException e) {
+ throw new Error(e);
+ } catch (ClassNotFoundException e) {
+ baseResourceURL = expandDir.toURI().toURL();
+ } catch (InvocationTargetException e) {
+ throw new Error(e);
+ } catch (IllegalAccessException e) {
+ throw new Error(e);
+ }
}
File disableFile = new File(archive.getPath() + ".disabled");
if (disableFile.exists()) {
@@ -287,13 +304,19 @@ public class ClassicPluginStrategy implements PluginStrategy {
// don't fix the dependency for yourself, or else we'll have a cycle
String yourName = atts.getValue("Short-Name");
if (shortName.equals(yourName)) return;
+ if (BREAK_CYCLES.contains(yourName + '/' + shortName)) {
+ LOGGER.log(Level.FINE, "skipping implicit dependency {0} → {1}", new Object[] {yourName, shortName});
+ return;
+ }
// some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
String jenkinsVersion = atts.getValue("Jenkins-Version");
if (jenkinsVersion==null)
jenkinsVersion = atts.getValue("Hudson-Version");
- if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(splitWhen) <= 0)
- optionalDependencies.add(new PluginWrapper.Dependency(shortName+':'+requireVersion));
+ if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(splitWhen) <= 0) {
+ optionalDependencies.add(new PluginWrapper.Dependency(shortName + ':' + requireVersion));
+ LOGGER.log(Level.FINE, "adding implicit dependency {0} → {1} because of {2}", new Object[] {yourName, shortName, jenkinsVersion});
+ }
}
}
@@ -314,6 +337,14 @@ public class ClassicPluginStrategy implements PluginStrategy {
new DetachedPlugin("junit","1.577.*","1.0")
);
+ /** Implicit dependencies that are known to be unnecessary and which must be cut out to prevent a dependency cycle among bundled plugins. */
+ private static final Set BREAK_CYCLES = new HashSet(Arrays.asList(
+ "script-security/matrix-auth",
+ "script-security/windows-slaves",
+ "script-security/antisamy-markup-formatter",
+ "script-security/matrix-project"
+ ));
+
/**
* Computes the classloader that takes the class masking into account.
*
diff --git a/core/src/main/java/hudson/DescriptorExtensionList.java b/core/src/main/java/hudson/DescriptorExtensionList.java
index a3917b2e84..513c557d44 100644
--- a/core/src/main/java/hudson/DescriptorExtensionList.java
+++ b/core/src/main/java/hudson/DescriptorExtensionList.java
@@ -81,6 +81,7 @@ public class DescriptorExtensionList, D extends Descrip
* @deprecated as of 1.416
* Use {@link #create(Jenkins, Class)}
*/
+ @Deprecated
public static ,D extends Descriptor>
DescriptorExtensionList createDescriptorList(Hudson hudson, Class describableType) {
return (DescriptorExtensionList)createDescriptorList((Jenkins)hudson,describableType);
@@ -95,6 +96,7 @@ public class DescriptorExtensionList, D extends Descrip
* @deprecated as of 1.416
* Use {@link #DescriptorExtensionList(Jenkins, Class)}
*/
+ @Deprecated
protected DescriptorExtensionList(Hudson hudson, Class describableType) {
this((Jenkins)hudson,describableType);
}
@@ -109,6 +111,7 @@ public class DescriptorExtensionList, D extends Descrip
*
* @param fqcn
* Fully qualified name of the descriptor, not the describable.
+ * @deprecated {@link Descriptor#getId} is supposed to be used for new code, not the descriptor class name.
*/
public D find(String fqcn) {
return Descriptor.find(this,fqcn);
diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java
index b2d61ead56..c0047b5578 100644
--- a/core/src/main/java/hudson/EnvVars.java
+++ b/core/src/main/java/hudson/EnvVars.java
@@ -359,6 +359,7 @@ public class EnvVars extends TreeMap {
/**
* Add a key/value but only if the value is not-null. Otherwise no-op.
+ * @since 1.556
*/
public void putIfNotNull(String key, String value) {
if (value!=null)
diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java
index d4dc1ef31b..4f0719da59 100644
--- a/core/src/main/java/hudson/ExtensionFinder.java
+++ b/core/src/main/java/hudson/ExtensionFinder.java
@@ -85,6 +85,7 @@ public abstract class ExtensionFinder implements ExtensionPoint {
* Use and implement {@link #find(Class,Hudson)} that allows us to put some metadata.
*/
@Restricted(NoExternalUse.class)
+ @Deprecated
public Collection findExtensions(Class type, Hudson hudson) {
return Collections.emptyList();
}
diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java
index 7861de26c2..46447fbcda 100644
--- a/core/src/main/java/hudson/ExtensionList.java
+++ b/core/src/main/java/hudson/ExtensionList.java
@@ -73,6 +73,7 @@ public class ExtensionList extends AbstractList {
* @deprecated as of 1.417
* Use {@link #jenkins}
*/
+ @Deprecated
public final Hudson hudson;
public final @CheckForNull Jenkins jenkins;
public final Class extensionType;
@@ -83,6 +84,8 @@ public class ExtensionList extends AbstractList {
@CopyOnWrite
private volatile List> extensions;
+ private final List listeners = new CopyOnWriteArrayList();
+
/**
* Place to store manually registered instances with the per-Hudson scope.
* {@link CopyOnWriteArrayList} is used here to support concurrent iterations and mutation.
@@ -93,6 +96,7 @@ public class ExtensionList extends AbstractList {
* @deprecated as of 1.416
* Use {@link #ExtensionList(Jenkins, Class)}
*/
+ @Deprecated
protected ExtensionList(Hudson hudson, Class extensionType) {
this((Jenkins)hudson,extensionType);
}
@@ -105,6 +109,7 @@ public class ExtensionList extends AbstractList {
* @deprecated as of 1.416
* Use {@link #ExtensionList(Jenkins, Class, CopyOnWriteArrayList)}
*/
+ @Deprecated
protected ExtensionList(Hudson hudson, Class extensionType, CopyOnWriteArrayList> legacyStore) {
this((Jenkins)hudson,extensionType,legacyStore);
}
@@ -126,11 +131,19 @@ public class ExtensionList extends AbstractList {
}
}
+ /**
+ * Add a listener to the extension list.
+ * @param listener The listener.
+ */
+ public void addListener(@Nonnull ExtensionListListener listener) {
+ listeners.add(listener);
+ }
+
/**
* Looks for the extension instance of the given type (subclasses excluded),
* or return null.
*/
- public U get(Class type) {
+ public @CheckForNull U get(Class type) {
for (T ext : this)
if(ext.getClass()==type)
return type.cast(ext);
@@ -180,7 +193,17 @@ public class ExtensionList extends AbstractList {
}
@Override
- public synchronized boolean remove(Object o) {
+ public boolean remove(Object o) {
+ try {
+ return removeSync(o);
+ } finally {
+ if(extensions!=null) {
+ fireOnChangeListeners();
+ }
+ }
+ }
+
+ private synchronized boolean removeSync(Object o) {
boolean removed = removeComponent(legacyInstances, o);
if(extensions!=null) {
List> r = new ArrayList>(extensions);
@@ -214,7 +237,18 @@ public class ExtensionList extends AbstractList {
* Prefer automatic registration.
*/
@Override
- public synchronized boolean add(T t) {
+ @Deprecated
+ public boolean add(T t) {
+ try {
+ return addSync(t);
+ } finally {
+ if(extensions!=null) {
+ fireOnChangeListeners();
+ }
+ }
+ }
+
+ private synchronized boolean addSync(T t) {
legacyInstances.add(new ExtensionComponent(t));
// if we've already filled extensions, add it
if(extensions!=null) {
@@ -270,6 +304,7 @@ public class ExtensionList extends AbstractList {
* Do not call from anywhere else.
*/
public void refresh(ExtensionComponentSet delta) {
+ boolean fireOnChangeListeners = false;
synchronized (getLoadLock()) {
if (extensions==null)
return; // not yet loaded. when we load it, we'll load everything visible by then, so no work needed
@@ -279,6 +314,20 @@ public class ExtensionList extends AbstractList {
List> l = Lists.newArrayList(extensions);
l.addAll(found);
extensions = sort(l);
+ fireOnChangeListeners = true;
+ }
+ }
+ if (fireOnChangeListeners) {
+ fireOnChangeListeners();
+ }
+ }
+
+ private void fireOnChangeListeners() {
+ for (ExtensionListListener listener : listeners) {
+ try {
+ listener.onChange();
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Error firing ExtensionListListener.onChange().", e);
}
}
}
@@ -325,6 +374,7 @@ public class ExtensionList extends AbstractList {
* @deprecated as of 1.416
* Use {@link #create(Jenkins, Class)}
*/
+ @Deprecated
public static ExtensionList create(Hudson hudson, Class type) {
return create((Jenkins)hudson,type);
}
diff --git a/core/src/main/java/hudson/ExtensionListListener.java b/core/src/main/java/hudson/ExtensionListListener.java
new file mode 100644
index 0000000000..26950b985a
--- /dev/null
+++ b/core/src/main/java/hudson/ExtensionListListener.java
@@ -0,0 +1,42 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2015, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson;
+
+/**
+ * {@link ExtensionList} listener.
+ *
+ * @author tom.fennelly@gmail.com
+ * @since TODO
+ */
+public abstract class ExtensionListListener {
+
+ /**
+ * {@link ExtensionList} contents has changed.
+ *
+ * This would be called when an entry gets added to or removed from the list for any reason e.g.
+ * when a dynamically loaded plugin introduces a new {@link ExtensionPoint} implementation
+ * that adds an entry to the {@link ExtensionList} being listened to.
+ */
+ public abstract void onChange();
+}
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index 74760a4288..d2778ba37e 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -367,6 +367,7 @@ public final class FilePath implements Serializable {
*
* @deprecated as of 1.315. Use {@link #zip(OutputStream)} that has more consistent name.
*/
+ @Deprecated
public void createZipArchive(OutputStream os) throws IOException, InterruptedException {
zip(os);
}
@@ -411,6 +412,7 @@ public final class FilePath implements Serializable {
* @deprecated as of 1.315
* Use {@link #zip(OutputStream,String)} that has more consistent name.
*/
+ @Deprecated
public void createZipArchive(OutputStream os, final String glob) throws IOException, InterruptedException {
archive(ArchiverFactory.ZIP,os,glob);
}
@@ -734,32 +736,34 @@ public final class FilePath implements Serializable {
* that supports upgrade and downgrade. Specifically,
*
*
- *
If the target directory doesn't exist {@linkplain #mkdirs() it'll be created}.
- *
The timestamp of the .tgz file is left in the installation directory upon extraction.
- *
If the timestamp left in the directory doesn't match with the timestamp of the current archive file,
+ *
If the target directory doesn't exist {@linkplain #mkdirs() it will be created}.
+ *
The timestamp of the archive is left in the installation directory upon extraction.
+ *
If the timestamp left in the directory does not match the timestamp of the current archive file,
* the directory contents will be discarded and the archive file will be re-extracted.
*
If the connection is refused but the target directory already exists, it is left alone.
*
*
* @param archive
- * The resource that represents the tgz/zip file. This URL must support the "Last-Modified" header.
- * (Most common usage is to get this from {@link ClassLoader#getResource(String)})
+ * The resource that represents the tgz/zip file. This URL must support the {@code Last-Modified} header.
+ * (For example, you could use {@link ClassLoader#getResource}.)
* @param listener
* If non-null, a message will be printed to this listener once this method decides to
- * extract an archive.
+ * extract an archive, or if there is any issue.
+ * @param message a message to be printed in case extraction will proceed.
* @return
* true if the archive was extracted. false if the extraction was skipped because the target directory
* was considered up to date.
* @since 1.299
*/
- public boolean installIfNecessaryFrom(URL archive, TaskListener listener, String message) throws IOException, InterruptedException {
+ public boolean installIfNecessaryFrom(@Nonnull URL archive, @CheckForNull TaskListener listener, @Nonnull String message) throws IOException, InterruptedException {
try {
FilePath timestamp = this.child(".timestamp");
+ long lastModified = timestamp.lastModified();
URLConnection con;
try {
con = ProxyConfiguration.open(archive);
- if (timestamp.exists()) {
- con.setIfModifiedSince(timestamp.lastModified());
+ if (lastModified != 0) {
+ con.setIfModifiedSince(lastModified);
}
con.connect();
} catch (IOException x) {
@@ -774,15 +778,21 @@ public final class FilePath implements Serializable {
}
}
- if (con instanceof HttpURLConnection
- && ((HttpURLConnection)con).getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
- return false;
+ if (lastModified != 0 && con instanceof HttpURLConnection) {
+ HttpURLConnection httpCon = (HttpURLConnection) con;
+ int responseCode = httpCon.getResponseCode();
+ if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
+ return false;
+ } else if (responseCode != HttpURLConnection.HTTP_OK) {
+ listener.getLogger().println("Skipping installation of " + archive + " to " + remote + " due to server error: " + responseCode + " " + httpCon.getResponseMessage());
+ return false;
+ }
}
long sourceTimestamp = con.getLastModified();
if(this.exists()) {
- if(timestamp.exists() && sourceTimestamp ==timestamp.lastModified())
+ if (lastModified != 0 && sourceTimestamp == lastModified)
return false; // already up to date
this.deleteContents();
} else {
@@ -1573,6 +1583,7 @@ public final class FilePath implements Serializable {
act(new SecureFileCallable() {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
+ // TODO first check for Java 7+ and use PosixFileAttributeView
_chmod(writing(f), mask);
return null;
@@ -1584,7 +1595,9 @@ public final class FilePath implements Serializable {
* Run chmod via jnr-posix
*/
private static void _chmod(File f, int mask) throws IOException {
- if (Functions.isWindows()) return; // noop
+ // TODO WindowsPosix actually does something here (WindowsLibC._wchmod); should we let it?
+ // Anyway the existing calls already skip this method if on Windows.
+ if (File.pathSeparatorChar==';') return; // noop
PosixAPI.jnr().chmod(f.getAbsolutePath(),mask);
}
@@ -2326,6 +2339,7 @@ public final class FilePath implements Serializable {
* @see #validateFileMask(FilePath, String)
* @deprecated use {@link #validateAntFileMask(String, int)} instead
*/
+ @Deprecated
public String validateAntFileMask(final String fileMasks) throws IOException, InterruptedException {
return validateAntFileMask(fileMasks, Integer.MAX_VALUE);
}
diff --git a/core/src/main/java/hudson/FileSystemProvisioner.java b/core/src/main/java/hudson/FileSystemProvisioner.java
index 58680eb888..15932a1dca 100644
--- a/core/src/main/java/hudson/FileSystemProvisioner.java
+++ b/core/src/main/java/hudson/FileSystemProvisioner.java
@@ -204,6 +204,7 @@ public abstract class FileSystemProvisioner implements ExtensionPoint, Describab
/**
* @deprecated as of 1.350
*/
+ @Deprecated
public WorkspaceSnapshot snapshot(AbstractBuild, ?> build, FilePath ws, TaskListener listener) throws IOException, InterruptedException {
return snapshot(build, ws, "**/*", listener);
}
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index b3fd046b10..970e3b0f62 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -28,6 +28,7 @@ package hudson;
import hudson.cli.CLICommand;
import hudson.console.ConsoleAnnotationDescriptor;
import hudson.console.ConsoleAnnotatorFactory;
+import hudson.init.InitMilestone;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Describable;
@@ -200,7 +201,25 @@ public class Functions {
public static String rfc822Date(Calendar cal) {
return Util.RFC822_DATETIME_FORMATTER.format(cal.getTime());
}
-
+
+ /**
+ * During Jenkins start-up, before {@link InitMilestone#PLUGINS_STARTED} the extensions lists will be empty
+ * and they are not guaranteed to be fully populated until after {@link InitMilestone#EXTENSIONS_AUGMENTED}.
+ * If you attempt to access the extensions list from a UI thread while the extensions are being loaded you will
+ * hit a big honking great monitor lock that will block until the effective extension list has been determined
+ * (as if a plugin fails to start, all of the failed plugin's extensions and any dependent plugins' extensions
+ * will have to be evicted from the list of extensions. In practical terms this only affects the
+ * "Jenkins is loading" screen, but as that screen uses the generic layouts we provide this utility method
+ * so that the generic layouts can avoid iterating extension lists while Jenkins is starting up.
+ *
+ * @return {@code true} if the extensions lists have been populated.
+ * @since 1.607
+ */
+ public static boolean isExtensionsAvailable() {
+ final Jenkins jenkins = Jenkins.getInstance();
+ return jenkins != null && jenkins.getInitLevel().compareTo(InitMilestone.EXTENSIONS_AUGMENTED) >= 0;
+ }
+
public static void initPageVariables(JellyContext context) {
StaplerRequest currentRequest = Stapler.getCurrentRequest();
String rootURL = currentRequest.getContextPath();
@@ -418,6 +437,7 @@ public class Functions {
* JEXL now supports the real ternary operator "x?y:z", so this work around
* is no longer necessary.
*/
+ @Deprecated
public static Object ifThenElse(boolean cond, Object thenValue, Object elseValue) {
return cond ? thenValue : elseValue;
}
@@ -1585,6 +1605,7 @@ public class Functions {
* @deprecated
* Use {@link #calcCheckUrl}
*/
+ @Deprecated
public String getCheckUrl(String userDefined, Object descriptor, String field) {
if(userDefined!=null || field==null) return userDefined;
if (descriptor instanceof Descriptor) {
@@ -1835,6 +1856,7 @@ public class Functions {
* @deprecated as of 1.451
* Use {@link #getAvatar}
*/
+ @Deprecated
public String getUserAvatar(User user, String avatarSize) {
return getAvatar(user,avatarSize);
}
@@ -1899,9 +1921,10 @@ public class Functions {
TcpSlaveAgentListener tal = j.tcpSlaveAgentListener;
if (tal !=null) {
- rsp.setIntHeader("X-Hudson-CLI-Port", tal.getPort());
- rsp.setIntHeader("X-Jenkins-CLI-Port", tal.getPort());
- rsp.setIntHeader("X-Jenkins-CLI2-Port", tal.getPort());
+ int p = TcpSlaveAgentListener.CLI_PORT !=null ? TcpSlaveAgentListener.CLI_PORT : tal.getPort();
+ rsp.setIntHeader("X-Hudson-CLI-Port", p);
+ rsp.setIntHeader("X-Jenkins-CLI-Port", p);
+ rsp.setIntHeader("X-Jenkins-CLI2-Port", p);
rsp.setHeader("X-Jenkins-CLI-Host", TcpSlaveAgentListener.CLI_HOST_NAME);
}
}
diff --git a/core/src/main/java/hudson/Launcher.java b/core/src/main/java/hudson/Launcher.java
index be003dfb64..974fc2cba7 100644
--- a/core/src/main/java/hudson/Launcher.java
+++ b/core/src/main/java/hudson/Launcher.java
@@ -133,6 +133,7 @@ public abstract class Launcher {
* figure out the current {@link Computer} from within a build, use
* {@link Computer#currentComputer()}
*/
+ @Deprecated
public Computer getComputer() {
for( Computer c : Jenkins.getInstance().getComputers() )
if(c.getChannel()==channel)
@@ -412,6 +413,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public final Proc launch(String cmd, Map env, OutputStream out, FilePath workDir) throws IOException {
return launch(cmd,Util.mapToEnv(env),out,workDir);
}
@@ -420,6 +422,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public final Proc launch(String[] cmd, Map env, OutputStream out, FilePath workDir) throws IOException {
return launch(cmd, Util.mapToEnv(env), out, workDir);
}
@@ -428,6 +431,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public final Proc launch(String[] cmd, Map env, InputStream in, OutputStream out) throws IOException {
return launch(cmd, Util.mapToEnv(env), in, out);
}
@@ -449,6 +453,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public final Proc launch(String[] cmd, boolean[] mask, Map env, OutputStream out, FilePath workDir) throws IOException {
return launch(cmd, mask, Util.mapToEnv(env), out, workDir);
}
@@ -470,6 +475,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public final Proc launch(String[] cmd, boolean[] mask, Map env, InputStream in, OutputStream out) throws IOException {
return launch(cmd, mask, Util.mapToEnv(env), in, out);
}
@@ -478,6 +484,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public final Proc launch(String cmd,String[] env,OutputStream out, FilePath workDir) throws IOException {
return launch(Util.tokenize(cmd),env,out,workDir);
}
@@ -486,6 +493,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public final Proc launch(String[] cmd, String[] env, OutputStream out, FilePath workDir) throws IOException {
return launch(cmd, env, null, out, workDir);
}
@@ -494,6 +502,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public final Proc launch(String[] cmd, String[] env, InputStream in, OutputStream out) throws IOException {
return launch(cmd, env, in, out, null);
}
@@ -515,6 +524,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public final Proc launch(String[] cmd, boolean[] mask, String[] env, OutputStream out, FilePath workDir) throws IOException {
return launch(cmd, mask, env, null, out, workDir);
}
@@ -536,6 +546,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public final Proc launch(String[] cmd, boolean[] mask, String[] env, InputStream in, OutputStream out) throws IOException {
return launch(cmd, mask, env, in, out, null);
}
@@ -554,6 +565,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public Proc launch(String[] cmd, String[] env, InputStream in, OutputStream out, FilePath workDir) throws IOException {
return launch(launch().cmds(cmd).envs(env).stdin(in).stdout(out).pwd(workDir));
}
@@ -576,6 +588,7 @@ public abstract class Launcher {
* @deprecated as of 1.311
* Use {@link #launch()} and its associated builder pattern
*/
+ @Deprecated
public Proc launch(String[] cmd, boolean[] mask, String[] env, InputStream in, OutputStream out, FilePath workDir) throws IOException {
return launch(launch().cmds(cmd).masks(mask).envs(env).stdin(in).stdout(out).pwd(workDir));
}
diff --git a/core/src/main/java/hudson/MarkupText.java b/core/src/main/java/hudson/MarkupText.java
index 4a56b1eb60..5240cd3bdf 100644
--- a/core/src/main/java/hudson/MarkupText.java
+++ b/core/src/main/java/hudson/MarkupText.java
@@ -283,6 +283,7 @@ public class MarkupText extends AbstractMarkupText {
* Use {@link #toString(boolean)} to be explicit about the escape mode.
*/
@Override
+ @Deprecated
public String toString() {
return toString(false);
}
diff --git a/core/src/main/java/hudson/Plugin.java b/core/src/main/java/hudson/Plugin.java
index 1e11d23bcd..4808f208ed 100644
--- a/core/src/main/java/hudson/Plugin.java
+++ b/core/src/main/java/hudson/Plugin.java
@@ -164,6 +164,7 @@ public abstract class Plugin implements Saveable {
* @since 1.233
* @deprecated as of 1.305 override {@link #configure(StaplerRequest,JSONObject)} instead
*/
+ @Deprecated
public void configure(JSONObject formData) throws IOException, ServletException, FormException {
}
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index d2918a7aa4..443716eed5 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -1,18 +1,18 @@
/*
* The MIT License
- *
+ *
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Stephen Connolly, Tom Huybrechts
- *
+ *
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
- *
+ *
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
- *
+ *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -51,6 +51,8 @@ import jenkins.RestartRequiredException;
import jenkins.YesNoMaybe;
import jenkins.model.Jenkins;
import jenkins.util.io.OnMaster;
+import jenkins.util.xml.RestrictiveEntityResolver;
+
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.fileupload.FileItem;
@@ -109,6 +111,7 @@ import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
@@ -148,6 +151,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
* {@link PluginManager} can now live longer than {@link jenkins.model.Jenkins} instance, so
* use {@code Hudson.getInstance().servletContext} instead.
*/
+ @Deprecated
public final ServletContext context;
/**
@@ -175,7 +179,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
* as the 2nd part can be repeated for each Hudson instance.
*/
private boolean pluginListed = false;
-
+
/**
* Strategy for creating and initializing plugins
*/
@@ -192,7 +196,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
this.rootDir = rootDir;
if(!rootDir.exists())
rootDir.mkdirs();
-
+
strategy = createPluginStrategy();
// load up rules for the core first
@@ -297,18 +301,18 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
r.add(p);
}
}
-
+
@Override
protected void reactOnCycle(PluginWrapper q, List cycle)
throws hudson.util.CyclicGraphDetector.CycleDetectedException {
-
+
LOGGER.log(Level.SEVERE, "found cycle in plugin dependencies: (root="+q+", deactivating all involved) "+Util.join(cycle," -> "));
for (PluginWrapper pluginWrapper : cycle) {
pluginWrapper.setHasCycleDependency(true);
failedPlugins.add(new FailedPlugin(pluginWrapper.getShortName(), new CycleDetectedException(cycle)));
}
}
-
+
};
cgd.run(getPlugins());
@@ -410,7 +414,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
/*
* contains operation that considers xxx.hpi and xxx.jpi as equal
- * this is necessary since the bundled plugins are still called *.hpi
+ * this is necessary since the bundled plugins are still called *.hpi
*/
private boolean containsHpiJpi(Collection bundledPlugins, String name) {
return bundledPlugins.contains(name.replaceAll("\\.hpi",".jpi"))
@@ -484,7 +488,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
} catch (ReactorException e) {
throw new IOException("Failed to initialize "+ sn +" plugin",e);
}
-
+
// recalculate dependencies of plugins optionally depending the newly deployed one.
for (PluginWrapper depender: plugins) {
if (depender.equals(p)) {
@@ -504,8 +508,8 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
}
}
-
- LOGGER.info("Plugin " + sn + " dynamically installed");
+
+ LOGGER.info("Plugin " + p.getShortName()+":"+p.getVersion() + " dynamically installed");
}
/**
@@ -532,7 +536,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
// normalization first, if the old file exists.
rename(new File(rootDir,legacyName),file);
rename(new File(rootDir,legacyName+".pinned"),pinFile);
-
+
// update file if:
// - no file exists today
// - bundled version and current version differs (by timestamp), and the file isn't pinned.
@@ -589,7 +593,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
/**
- * Creates a hudson.PluginStrategy, looking at the corresponding system property.
+ * Creates a hudson.PluginStrategy, looking at the corresponding system property.
*/
protected PluginStrategy createPluginStrategy() {
String strategyName = System.getProperty(PluginStrategy.class.getName());
@@ -602,7 +606,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
LOGGER.info("Plugin strategy: " + strategyName);
return (PluginStrategy) strategy;
} else {
- LOGGER.warning("Plugin strategy (" + strategyName +
+ LOGGER.warning("Plugin strategy (" + strategyName +
") is not an instance of hudson.PluginStrategy");
}
} catch (ClassNotFoundException e) {
@@ -614,7 +618,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
LOGGER.info("Falling back to ClassicPluginStrategy");
}
-
+
// default and fallback
return new ClassicPluginStrategy(this);
}
@@ -801,7 +805,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
rsp.sendRedirect("../updateCenter/");
}
-
+
/**
* Bare-minimum configuration mechanism to change the update center.
@@ -816,7 +820,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
sites.remove(s);
}
sites.add(new UpdateSite(UpdateCenter.ID_DEFAULT, site));
-
+
return HttpResponses.redirectToContextRoot();
}
@@ -835,7 +839,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
return new HttpRedirect("advanced");
}
-
+
/**
* Uploads a plugin.
*/
@@ -851,8 +855,8 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
if("".equals(fileName)){
return new HttpRedirect("advanced");
}
- // we allow the upload of the new jpi's and the legacy hpi's
- if(!fileName.endsWith(".jpi") && !fileName.endsWith(".hpi")){
+ // we allow the upload of the new jpi's and the legacy hpi's
+ if(!fileName.endsWith(".jpi") && !fileName.endsWith(".hpi")){
throw new Failure(hudson.model.Messages.Hudson_NotAPlugin(fileName));
}
@@ -1045,6 +1049,12 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
requestedPlugins.put(shortName, requested);
}
}
+
+ @Override public InputSource resolveEntity(String publicId, String systemId) throws IOException,
+ SAXException {
+ return RestrictiveEntityResolver.INSTANCE.resolveEntity(publicId, systemId);
+ }
+
});
} catch (SAXException x) {
throw new IOException("Failed to parse XML",x);
@@ -1175,10 +1185,10 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
private static final Logger LOGGER = Logger.getLogger(PluginManager.class.getName());
public static boolean FAST_LOOKUP = !Boolean.getBoolean(PluginManager.class.getName()+".noFastLookup");
-
+
public static final Permission UPLOAD_PLUGINS = new Permission(Jenkins.PERMISSIONS, "UploadPlugins", Messages._PluginManager_UploadPluginsPermission_Description(),Jenkins.ADMINISTER,PermissionScope.JENKINS);
public static final Permission CONFIGURE_UPDATECENTER = new Permission(Jenkins.PERMISSIONS, "ConfigureUpdateCenter", Messages._PluginManager_ConfigureUpdateCenterPermission_Description(),Jenkins.ADMINISTER,PermissionScope.JENKINS);
-
+
/**
* Remembers why a plugin failed to deploy.
*/
@@ -1202,17 +1212,17 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
/*package*/ static final class PluginInstanceStore {
final Map store = new Hashtable();
}
-
+
/**
* {@link AdministrativeMonitor} that checks if there are any plugins with cycle dependencies.
*/
@Extension
public static final class PluginCycleDependenciesMonitor extends AdministrativeMonitor {
-
+
private transient volatile boolean isActive = false;
-
- private transient volatile List pluginsWithCycle;
-
+
+ private transient volatile List pluginsWithCycle;
+
public boolean isActivated() {
if(pluginsWithCycle == null){
pluginsWithCycle = new ArrayList();
@@ -1230,16 +1240,16 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
return pluginsWithCycle;
}
}
-
+
/**
* {@link AdministrativeMonitor} that informs the administrator about a required plugin update.
* @since 1.491
*/
@Extension
public static final class PluginUpdateMonitor extends AdministrativeMonitor {
-
+
private Map pluginsToBeUpdated = new HashMap();
-
+
/**
* Convenience method to ease access to this monitor, this allows other plugins to register required updates.
* @return this monitor.
@@ -1247,10 +1257,10 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
public static final PluginUpdateMonitor getInstance() {
return ExtensionList.lookup(PluginUpdateMonitor.class).get(0);
}
-
+
/**
* Report to the administrator if the plugin with the given name is older then the required version.
- *
+ *
* @param pluginName shortName of the plugin (artifactId)
* @param requiredVersion the lowest version which is OK (e.g. 2.2.2)
* @param message the message to show (plain text)
@@ -1267,20 +1277,20 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
public boolean isActivated() {
return !pluginsToBeUpdated.isEmpty();
}
-
+
/**
- * adds a message about a plugin to the manage screen
+ * adds a message about a plugin to the manage screen
* @param pluginName the plugins name
* @param message the message to be displayed
*/
public void addPluginToUpdate(String pluginName, String message) {
this.pluginsToBeUpdated.put(pluginName, new PluginUpdateInfo(pluginName, message));
}
-
+
public Collection getPluginsToBeUpdated() {
return pluginsToBeUpdated.values();
}
-
+
public static class PluginUpdateInfo {
public final String pluginName;
public final String message;
@@ -1290,5 +1300,5 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
}
- }
+ }
}
diff --git a/core/src/main/java/hudson/Proc.java b/core/src/main/java/hudson/Proc.java
index 5114cbd2c4..598fbca374 100644
--- a/core/src/main/java/hudson/Proc.java
+++ b/core/src/main/java/hudson/Proc.java
@@ -431,6 +431,7 @@ public abstract class Proc {
*
* @deprecated as of 1.399. Replaced by {@link Launcher.RemoteLauncher.ProcImpl}
*/
+ @Deprecated
public static final class RemoteProc extends Proc {
private final Future process;
diff --git a/core/src/main/java/hudson/ProxyConfiguration.java b/core/src/main/java/hudson/ProxyConfiguration.java
index 0e36a4cd9d..9b51990925 100644
--- a/core/src/main/java/hudson/ProxyConfiguration.java
+++ b/core/src/main/java/hudson/ProxyConfiguration.java
@@ -165,6 +165,7 @@ public final class ProxyConfiguration extends AbstractDescribableImpl properties) {
return replaceMacro(s,new VariableResolver.ByMap(properties));
}
-
+
/**
* Replaces the occurrence of '$key' by resolver.get('key').
*
@@ -143,7 +143,7 @@ public class Util {
if (s == null) {
return null;
}
-
+
int idx=0;
while(true) {
Matcher m = VARIABLE.matcher(s);
@@ -370,6 +370,30 @@ public class Util {
}
}
+ /**
+ * A mostly accurate check of whether a path is a relative path or not. This is designed to take a path against
+ * an unknown operating system so may give invalid results.
+ *
+ * @param path the path.
+ * @return {@code true} if the path looks relative.
+ * @since 1.606
+ */
+ public static boolean isRelativePath(String path) {
+ if (path.startsWith("/"))
+ return false;
+ if (path.startsWith("\\\\") && path.length() > 3 && path.indexOf('\\', 3) != -1)
+ return false; // a UNC path which is the most absolute you can get on windows
+ if (path.length() >= 3 && ':' == path.charAt(1)) {
+ // never mind that the drive mappings can be changed between sessions, we just want to
+ // know if the 3rd character is a `\` (or a '/' is acceptable too)
+ char p = path.charAt(0);
+ if (('A' <= p && p <= 'Z') || ('a' <= p && p <= 'z')) {
+ return path.charAt(2) != '\\' && path.charAt(2) != '/';
+ }
+ }
+ return true;
+ }
+
/**
* Creates a new temporary directory.
*/
@@ -624,7 +648,7 @@ public class Util {
* Converts a string into 128-bit AES key.
* @since 1.308
*/
- @Nonnull
+ @Nonnull
public static SecretKey toAes128Key(@Nonnull String s) {
try {
// turn secretKey into 256 bit hash
@@ -743,13 +767,14 @@ public class Util {
/**
* Combines number and unit, with a plural suffix if needed.
- *
- * @deprecated
- * Use individual localization methods instead.
+ *
+ * @deprecated
+ * Use individual localization methods instead.
* See {@link Messages#Util_year(Object)} for an example.
* Deprecated since 2009-06-24, remove method after 2009-12-24.
*/
@Nonnull
+ @Deprecated
public static String combine(long n, @Nonnull String suffix) {
String s = Long.toString(n)+' '+suffix;
if(n!=1)
@@ -780,7 +805,7 @@ public class Util {
* {@link #rawEncode(String)} should generally be used instead, though be careful to pass only
* a single path component to that method (it will encode /, but this method does not).
*/
- @Nonnull
+ @Nonnull
public static String encode(@Nonnull String s) {
try {
boolean escaped = false;
@@ -888,7 +913,7 @@ public class Util {
/**
* Escapes HTML unsafe characters like <, & to the respective character entities.
*/
- @Nonnull
+ @Nonnull
public static String escape(@Nonnull String text) {
if (text==null) return null;
StringBuilder buf = new StringBuilder(text.length()+64);
@@ -1112,7 +1137,7 @@ public class Util {
* @param symlinkPath
* Where to create a symlink in (relative to {@code baseDir})
*/
- public static void createSymlink(@Nonnull File baseDir, @Nonnull String targetPath,
+ public static void createSymlink(@Nonnull File baseDir, @Nonnull String targetPath,
@Nonnull String symlinkPath, @Nonnull TaskListener listener) throws InterruptedException {
try {
if (createSymlinkJava7(baseDir, targetPath, symlinkPath)) {
@@ -1245,6 +1270,7 @@ public class Util {
* @deprecated as of 1.456
* Use {@link #resolveSymlink(File)}
*/
+ @Deprecated
public static String resolveSymlink(File link, TaskListener listener) throws InterruptedException, IOException {
return resolveSymlink(link);
}
@@ -1346,7 +1372,7 @@ public class Util {
* @deprecated since 2008-05-13. This method is broken (see ISSUE#1666). It should probably
* be removed but I'm not sure if it is considered part of the public API
* that needs to be maintained for backwards compatibility.
- * Use {@link #encode(String)} instead.
+ * Use {@link #encode(String)} instead.
*/
@Deprecated
public static String encodeRFC2396(String url) {
@@ -1367,7 +1393,7 @@ public class Util {
s = ""+s+"";
return s;
}
-
+
/**
* Returns the parsed string if parsed successful; otherwise returns the default number.
* If the string is null, empty or a ParseException is thrown then the defaultNumber
diff --git a/core/src/main/java/hudson/cli/CliProtocol.java b/core/src/main/java/hudson/cli/CliProtocol.java
index 1d4f25627e..eecb4c5faf 100644
--- a/core/src/main/java/hudson/cli/CliProtocol.java
+++ b/core/src/main/java/hudson/cli/CliProtocol.java
@@ -48,6 +48,7 @@ public class CliProtocol extends AgentProtocol {
* @deprecated as of 1.559
* Use {@link #Handler(NioChannelHub, Socket)}
*/
+ @Deprecated
public Handler(Socket socket) {
this(null,socket);
}
diff --git a/core/src/main/java/hudson/cli/CliProtocol2.java b/core/src/main/java/hudson/cli/CliProtocol2.java
index 644914a089..0f2757df78 100644
--- a/core/src/main/java/hudson/cli/CliProtocol2.java
+++ b/core/src/main/java/hudson/cli/CliProtocol2.java
@@ -37,6 +37,7 @@ public class CliProtocol2 extends CliProtocol {
* @deprecated as of 1.559
* Use {@link #Handler2(NioChannelHub, Socket)}
*/
+ @Deprecated
public Handler2(Socket socket) {
super(socket);
}
diff --git a/core/src/main/java/hudson/cli/ListJobsCommand.java b/core/src/main/java/hudson/cli/ListJobsCommand.java
index bdb64208d5..97ea8d2967 100644
--- a/core/src/main/java/hudson/cli/ListJobsCommand.java
+++ b/core/src/main/java/hudson/cli/ListJobsCommand.java
@@ -82,7 +82,7 @@ public class ListJobsCommand extends CLICommand {
// Print all jobs.
for (TopLevelItem item : jobs) {
- stdout.println(item.getDisplayName());
+ stdout.println(item.getName());
}
return 0;
diff --git a/core/src/main/java/hudson/cli/declarative/MethodBinder.java b/core/src/main/java/hudson/cli/declarative/MethodBinder.java
index 6b13772758..f9b8885e8e 100644
--- a/core/src/main/java/hudson/cli/declarative/MethodBinder.java
+++ b/core/src/main/java/hudson/cli/declarative/MethodBinder.java
@@ -30,10 +30,12 @@ import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.spi.FieldSetter;
import org.kohsuke.args4j.spi.Setter;
import org.kohsuke.args4j.spi.OptionHandler;
import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
@@ -79,6 +81,16 @@ class MethodBinder {
public boolean isMultiValued() {
return false;
}
+
+ @Override
+ public FieldSetter asFieldSetter() {
+ return null;
+ }
+
+ @Override
+ public AnnotatedElement asAnnotatedElement() {
+ return p;
+ }
};
Option option = p.annotation(Option.class);
if (option!=null) {
@@ -148,5 +160,10 @@ class MethodBinder {
public Class extends Annotation> annotationType() {
return base.annotationType();
}
+
+ @Override
+ public boolean hidden() {
+ return base.hidden();
+ }
}
}
diff --git a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java
index f2e1b1eaa1..29a2187b93 100644
--- a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java
+++ b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java
@@ -355,10 +355,12 @@ public class OldDataMonitor extends AdministrativeMonitor {
private static SaveableReference referTo(Saveable s) {
if (s instanceof Run) {
- return new RunSaveableReference((Run) s);
- } else {
- return new SimpleSaveableReference(s);
+ Job parent = ((Run) s).getParent();
+ if (Jenkins.getInstance().getItemByFullName(parent.getFullName()) == parent) {
+ return new RunSaveableReference((Run) s);
+ }
}
+ return new SimpleSaveableReference(s);
}
private static final class SimpleSaveableReference implements SaveableReference {
diff --git a/core/src/main/java/hudson/init/TaskMethodFinder.java b/core/src/main/java/hudson/init/TaskMethodFinder.java
index 7747e850b6..9ced2cb1ab 100644
--- a/core/src/main/java/hudson/init/TaskMethodFinder.java
+++ b/core/src/main/java/hudson/init/TaskMethodFinder.java
@@ -1,5 +1,6 @@
package hudson.init;
+import com.google.inject.Injector;
import hudson.model.Hudson;
import jenkins.model.Jenkins;
import org.jvnet.hudson.annotation_indexer.Index;
@@ -116,6 +117,12 @@ abstract class TaskMethodFinder extends TaskBuilder {
private Object lookUp(Class> type) {
if (type==Jenkins.class || type==Hudson.class)
return Jenkins.getInstance();
+ Jenkins j = Jenkins.getInstance();
+ if (j!=null) {
+ Injector i = j.getInjector();
+ if (i!=null)
+ return i.getInstance(type);
+ }
throw new IllegalArgumentException("Unable to inject "+type);
}
diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java
index 28ef3ff5e8..aa1f9626e6 100644
--- a/core/src/main/java/hudson/model/AbstractBuild.java
+++ b/core/src/main/java/hudson/model/AbstractBuild.java
@@ -38,6 +38,8 @@ import hudson.model.Fingerprint.RangeSet;
import hudson.model.labels.LabelAtom;
import hudson.model.listeners.RunListener;
import hudson.model.listeners.SCMListener;
+import hudson.remoting.ChannelClosedException;
+import hudson.remoting.RequestAbortedException;
import hudson.scm.ChangeLogParser;
import hudson.scm.ChangeLogSet;
import hudson.scm.ChangeLogSet.Entry;
@@ -47,6 +49,7 @@ import hudson.scm.SCMRevisionState;
import hudson.slaves.NodeProperty;
import hudson.slaves.WorkspaceList;
import hudson.slaves.WorkspaceList.Lease;
+import hudson.slaves.OfflineCause;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.BuildTrigger;
@@ -410,6 +413,7 @@ public abstract class AbstractBuild
,R extends Abs
* @deprecated as of 1.467
* Please use {@link hudson.model.Run.RunExecution}
*/
+ @Deprecated
public abstract class AbstractRunner extends AbstractBuildExecution {
}
@@ -532,29 +536,13 @@ public abstract class AbstractBuild
,R extends Abs
Result result = doRun(listener);
- Computer c = node.toComputer();
- if (c==null || c.isOffline()) {
- // As can be seen in HUDSON-5073, when a build fails because of the slave connectivity problem,
- // error message doesn't point users to the slave. So let's do it here.
- listener.hyperlink("/computer/"+builtOn+"/log","Looks like the node went offline during the build. Check the slave log for the details.");
-
- if (c != null) {
- // grab the end of the log file. This might not work very well if the slave already
- // starts reconnecting. Fixing this requires a ring buffer in slave logs.
- AnnotatedLargeText log = c.getLogText();
- StringWriter w = new StringWriter();
- log.writeHtmlTo(Math.max(0,c.getLogFile().length()-10240),w);
-
- listener.getLogger().print(ExpandableDetailsNote.encodeTo("details",w.toString()));
- listener.getLogger().println();
- }
+ if (node.getChannel() != null) {
+ // kill run-away processes that are left
+ // use multiple environment variables so that people can escape this massacre by overriding an environment
+ // variable for some processes
+ launcher.kill(getCharacteristicEnvVars());
}
- // kill run-away processes that are left
- // use multiple environment variables so that people can escape this massacre by overriding an environment
- // variable for some processes
- launcher.kill(getCharacteristicEnvVars());
-
// this is ugly, but for historical reason, if non-null value is returned
// it should become the final result.
if (result==null) result = getResult();
@@ -704,6 +692,7 @@ public abstract class AbstractBuild
,R extends Abs
* @deprecated as of 1.356
* Use {@link #performAllBuildSteps(BuildListener, Map, boolean)}
*/
+ @Deprecated
protected final void performAllBuildStep(BuildListener listener, Map,? extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
performAllBuildSteps(listener,buildSteps.values(),phase);
}
@@ -716,6 +705,7 @@ public abstract class AbstractBuild
,R extends Abs
* @deprecated as of 1.356
* Use {@link #performAllBuildSteps(BuildListener, Iterable, boolean)}
*/
+ @Deprecated
protected final void performAllBuildStep(BuildListener listener, Iterable extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
performAllBuildSteps(listener,buildSteps,phase);
}
@@ -725,6 +715,8 @@ public abstract class AbstractBuild
,R extends Abs
*
* @param phase
* true for the post build processing, and false for the final "run after finished" execution.
+ *
+ * @return false if any build step failed
*/
protected final boolean performAllBuildSteps(BuildListener listener, Iterable extends BuildStep> buildSteps, boolean phase) throws InterruptedException, IOException {
boolean r = true;
@@ -734,20 +726,33 @@ public abstract class AbstractBuild
,R extends Abs
if (!perform(bs,listener)) {
LOGGER.log(Level.FINE, "{0} : {1} failed", new Object[] {AbstractBuild.this, bs});
r = false;
+ if (phase) {
+ setResult(Result.FAILURE);
+ }
}
} catch (Exception e) {
reportError(bs, e, listener, phase);
+ r = false;
} catch (LinkageError e) {
reportError(bs, e, listener, phase);
+ r = false;
}
}
return r;
}
private void reportError(BuildStep bs, Throwable e, BuildListener listener, boolean phase) {
- String msg = "Publisher " + bs.getClass().getName() + " aborted due to exception";
- e.printStackTrace(listener.error(msg));
- LOGGER.log(WARNING, msg, e);
+ final String publisher = ((Publisher) bs).getDescriptor().getDisplayName();
+
+ if (e instanceof AbortException) {
+ LOGGER.log(Level.FINE, "{0} : {1} failed", new Object[] {AbstractBuild.this, publisher});
+ listener.error("Publisher '" + publisher + "' failed: " + e.getMessage());
+ } else {
+ String msg = "Publisher '" + publisher + "' aborted due to exception: ";
+ e.printStackTrace(listener.error(msg));
+ LOGGER.log(WARNING, msg, e);
+ }
+
if (phase) {
setResult(Result.FAILURE);
}
@@ -767,7 +772,22 @@ public abstract class AbstractBuild
,R extends Abs
for (BuildStepListener bsl : BuildStepListener.all()) {
bsl.started(AbstractBuild.this, bs, listener);
}
- boolean canContinue = mon.perform(bs, AbstractBuild.this, launcher, listener);
+
+ boolean canContinue = false;
+ try {
+
+ canContinue = mon.perform(bs, AbstractBuild.this, launcher, listener);
+ } catch (RequestAbortedException ex) {
+ // Channel is closed, do not continue
+ reportBrokenChannel(listener);
+ } catch (ChannelClosedException ex) {
+ // Channel is closed, do not continue
+ reportBrokenChannel(listener);
+ } catch (RuntimeException ex) {
+
+ ex.printStackTrace(listener.error("Build step failed with exception"));
+ }
+
for (BuildStepListener bsl : BuildStepListener.all()) {
bsl.finished(AbstractBuild.this, bs, listener, canContinue);
}
@@ -783,6 +803,16 @@ public abstract class AbstractBuild
,R extends Abs
return canContinue;
}
+ private void reportBrokenChannel(BuildListener listener) throws IOException {
+ final Node node = getCurrentNode();
+ listener.hyperlink("/" + node.toComputer().getUrl() + "log", "Slave went offline during the build");
+ listener.getLogger().println();
+ final OfflineCause offlineCause = node.toComputer().getOfflineCause();
+ if (offlineCause != null) {
+ listener.error(offlineCause.toString());
+ }
+ }
+
private String getBuildStepName(BuildStep bs) {
if (bs instanceof Describable>) {
return ((Describable>) bs).getDescriptor().getDisplayName();
@@ -1039,6 +1069,7 @@ public abstract class AbstractBuild
,R extends Abs
/**
* @deprecated Use {@link #getAction(Class)} on {@link AbstractTestResultAction}.
*/
+ @Deprecated
public Action getTestResultAction() {
try {
return getAction(Jenkins.getInstance().getPluginManager().uberClassLoader.loadClass("hudson.tasks.test.AbstractTestResultAction").asSubclass(Action.class));
@@ -1050,6 +1081,7 @@ public abstract class AbstractBuild
,R extends Abs
/**
* @deprecated Use {@link #getAction(Class)} on {@link AggregatedTestResultAction}.
*/
+ @Deprecated
public Action getAggregatedTestResultAction() {
try {
return getAction(Jenkins.getInstance().getPluginManager().uberClassLoader.loadClass("hudson.tasks.test.AggregatedTestResultAction").asSubclass(Action.class));
@@ -1307,7 +1339,7 @@ public abstract class AbstractBuild
,R extends Abs
public List getBuilds() {
List r = new ArrayList();
- AbstractBuild,?> b = (AbstractBuild)project.getNearestBuild(fromId);
+ AbstractBuild,?> b = project.getNearestBuild(fromId);
if (b!=null && b.getNumber()==fromId)
b = b.getNextBuild(); // fromId exclusive
@@ -1328,6 +1360,7 @@ public abstract class AbstractBuild
,R extends Abs
* @deprecated as of 1.489
* Use {@link #doStop()}
*/
+ @Deprecated
public void doStop(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
doStop().generateResponse(req,rsp,this);
}
diff --git a/core/src/main/java/hudson/model/AbstractCIBase.java b/core/src/main/java/hudson/model/AbstractCIBase.java
index cf9cec9bf2..cf487105c2 100644
--- a/core/src/main/java/hudson/model/AbstractCIBase.java
+++ b/core/src/main/java/hudson/model/AbstractCIBase.java
@@ -34,7 +34,6 @@ import jenkins.model.Jenkins;
import org.kohsuke.stapler.StaplerFallback;
import org.kohsuke.stapler.StaplerProxy;
-import java.io.IOException;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Logger;
@@ -48,8 +47,6 @@ public abstract class AbstractCIBase extends Node implements ItemGroup computers = getComputerMap();
- for (Map.Entry e : computers.entrySet()) {
- if (e.getValue() == computer) {
- computers.remove(e.getKey());
- computer.onRemoved();
- return;
+ /*package*/ void removeComputer(final Computer computer) {
+ Queue.withLock(new Runnable() {
+ @Override
+ public void run() {
+ Map computers = getComputerMap();
+ for (Map.Entry e : computers.entrySet()) {
+ if (e.getValue() == computer) {
+ computers.remove(e.getKey());
+ computer.onRemoved();
+ return;
+ }
+ }
}
- }
+ });
}
/*package*/ @CheckForNull Computer getComputer(Node n) {
@@ -160,36 +163,46 @@ public abstract class AbstractCIBase extends Node implements ItemGroup computers = getComputerMap();
- synchronized(updateComputerLock) {// just so that we don't have two code updating computer list at the same time
- Map byName = new HashMap();
- for (Computer c : computers.values()) {
- Node node = c.getNode();
- if (node == null)
- continue; // this computer is gone
- byName.put(node.getNodeName(),c);
- }
+ protected void updateComputerList(final boolean automaticSlaveLaunch) {
+ final Map computers = getComputerMap();
+ final Set old = new HashSet(computers.size());
+ Queue.withLock(new Runnable() {
+ @Override
+ public void run() {
+ Map byName = new HashMap();
+ for (Computer c : computers.values()) {
+ old.add(c);
+ Node node = c.getNode();
+ if (node == null)
+ continue; // this computer is gone
+ byName.put(node.getNodeName(),c);
+ }
- final Set old = new HashSet(computers.values());
- Set used = new HashSet();
+ Set used = new HashSet(old.size());
- updateComputer(this, byName, used, automaticSlaveLaunch);
- for (Node s : getNodes()) {
- long start = System.currentTimeMillis();
- updateComputer(s, byName, used, automaticSlaveLaunch);
- if(LOG_STARTUP_PERFORMANCE)
- LOGGER.info(String.format("Took %dms to update node %s",
- System.currentTimeMillis()-start, s.getNodeName()));
- }
+ updateComputer(AbstractCIBase.this, byName, used, automaticSlaveLaunch);
+ for (Node s : getNodes()) {
+ long start = System.currentTimeMillis();
+ updateComputer(s, byName, used, automaticSlaveLaunch);
+ if(LOG_STARTUP_PERFORMANCE)
+ LOGGER.info(String.format("Took %dms to update node %s",
+ System.currentTimeMillis()-start, s.getNodeName()));
+ }
- // find out what computers are removed, and kill off all executors.
- // when all executors exit, it will be removed from the computers map.
- // so don't remove too quickly
- old.removeAll(used);
- for (Computer c : old) {
- killComputer(c);
+ // find out what computers are removed, and kill off all executors.
+ // when all executors exit, it will be removed from the computers map.
+ // so don't remove too quickly
+ old.removeAll(used);
+ // we need to start the process of reducing the executors on all computers as distinct
+ // from the killing action which should not excessively use the Queue lock.
+ for (Computer c : old) {
+ c.inflictMortalWound();
+ }
}
+ });
+ for (Computer c : old) {
+ // when we get to here, the number of executors should be zero so this call should not need the Queue.lock
+ killComputer(c);
}
getQueue().scheduleMaintenance();
for (ComputerListener cl : ComputerListener.all())
diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java
index 3524444fea..179b3ec92f 100644
--- a/core/src/main/java/hudson/model/AbstractItem.java
+++ b/core/src/main/java/hudson/model/AbstractItem.java
@@ -45,6 +45,8 @@ import jenkins.model.DirectlyModifiableTopLevelItemGroup;
import jenkins.model.Jenkins;
import jenkins.security.NotReallyRoleSensitiveCallable;
import org.acegisecurity.Authentication;
+import jenkins.util.xml.XMLUtils;
+
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.FileSet;
import org.kohsuke.stapler.WebMethod;
@@ -67,12 +69,11 @@ import org.kohsuke.stapler.HttpDeletable;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.interceptor.RequirePOST;
+import org.xml.sax.SAXException;
import javax.servlet.ServletException;
import javax.xml.transform.Source;
-import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
@@ -215,6 +216,8 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
protected void renameTo(final String newName) throws IOException {
// always synchronize from bigger objects first
final ItemGroup parent = getParent();
+ String oldName = this.name;
+ String oldFullName = getFullName();
synchronized (parent) {
synchronized (this) {
// sanity check
@@ -247,9 +250,6 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
}
});
-
- String oldName = this.name;
- String oldFullName = getFullName();
File oldRoot = this.getRootDir();
doSetName(newName);
@@ -323,10 +323,9 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
} catch (AbstractMethodError _) {
// ignore
}
-
- ItemListener.fireLocationChange(this, oldFullName);
}
}
+ ItemListener.fireLocationChange(this, oldFullName);
}
@@ -622,30 +621,30 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
* @deprecated as of 1.473
* Use {@link #updateByXml(Source)}
*/
+ @Deprecated
public void updateByXml(StreamSource source) throws IOException {
updateByXml((Source)source);
}
/**
- * Updates Job by its XML definition.
+ * Updates an Item by its XML definition.
+ * @param source source of the Item's new definition.
+ * The source should be either a StreamSource or a SAXSource, other
+ * sources may not be handled.
* @since 1.473
*/
public void updateByXml(Source source) throws IOException {
checkPermission(CONFIGURE);
XmlFile configXmlFile = getConfigFile();
- AtomicFileWriter out = new AtomicFileWriter(configXmlFile.getFile());
+ final AtomicFileWriter out = new AtomicFileWriter(configXmlFile.getFile());
try {
try {
- // this allows us to use UTF-8 for storing data,
- // plus it checks any well-formedness issue in the submitted
- // data
- Transformer t = TransformerFactory.newInstance()
- .newTransformer();
- t.transform(source,
- new StreamResult(out));
+ XMLUtils.safeTransform(source, new StreamResult(out));
out.close();
} catch (TransformerException e) {
throw new IOException("Failed to persist config.xml", e);
+ } catch (SAXException e) {
+ throw new IOException("Failed to persist config.xml", e);
}
// try to reflect the changes by reloading
@@ -667,6 +666,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
// if everything went well, commit this new version
out.commit();
SaveableListener.fireOnChange(this, getConfigFile());
+
} finally {
out.abort(); // don't leave anything behind
}
diff --git a/core/src/main/java/hudson/model/AbstractModelObject.java b/core/src/main/java/hudson/model/AbstractModelObject.java
index 301d6d09b3..847d545222 100644
--- a/core/src/main/java/hudson/model/AbstractModelObject.java
+++ b/core/src/main/java/hudson/model/AbstractModelObject.java
@@ -81,6 +81,7 @@ public abstract class AbstractModelObject implements SearchableModelObject {
* @deprecated
* Use {@link RequirePOST} on your method.
*/
+ @Deprecated
protected final void requirePOST() throws ServletException {
StaplerRequest req = Stapler.getCurrentRequest();
if (req==null) return; // invoked outside the context of servlet
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index fe49923fac..5fd056b711 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -323,7 +323,11 @@ public abstract class AbstractProject
,R extends A
builds = buildMixIn.getRunMap();
triggers().setOwner(this);
for (Trigger t : triggers()) {
- t.start(this, Items.currentlyUpdatingByXml());
+ try {
+ t.start(this, Items.currentlyUpdatingByXml());
+ } catch (Throwable e) {
+ LOGGER.log(Level.WARNING, "could not start trigger while loading project '" + getFullName() + "'", e);
+ }
}
if(scm==null)
scm = new NullSCM(); // perhaps it was pointing to a plugin that no longer exists.
@@ -505,6 +509,7 @@ public abstract class AbstractProject
,R extends A
* If you are calling this method to serve a file from the workspace, doing a form validation, etc., then
* use {@link #getSomeWorkspace()}
*/
+ @Deprecated
public final FilePath getWorkspace() {
AbstractBuild b = getBuildForDeprecatedMethods();
return b != null ? b.getWorkspace() : null;
@@ -586,6 +591,7 @@ public abstract class AbstractProject
,R extends A
* @deprecated as of 1.319
* See {@link #getWorkspace()} for a migration strategy.
*/
+ @Deprecated
public FilePath getModuleRoot() {
AbstractBuild b = getBuildForDeprecatedMethods();
return b != null ? b.getModuleRoot() : null;
@@ -601,6 +607,7 @@ public abstract class AbstractProject
,R extends A
* @deprecated as of 1.319
* See {@link #getWorkspace()} for a migration strategy.
*/
+ @Deprecated
public FilePath[] getModuleRoots() {
AbstractBuild b = getBuildForDeprecatedMethods();
return b != null ? b.getModuleRoots() : null;
@@ -751,8 +758,13 @@ public abstract class AbstractProject
,R extends A
for (JobProperty super P> p : Util.fixNull(properties))
ta.addAll(p.getJobActions((P)this));
- for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all())
- ta.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null
+ for (TransientProjectActionFactory tpaf : TransientProjectActionFactory.all()) {
+ try {
+ ta.addAll(Util.fixNull(tpaf.createFor(this))); // be defensive against null
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Could not load actions from " + tpaf + " for " + this, e);
+ }
+ }
return ta;
}
@@ -792,6 +804,7 @@ public abstract class AbstractProject
,R extends A
* @deprecated
* Use {@link #scheduleBuild(Cause)}. Since 1.283
*/
+ @Deprecated
public boolean scheduleBuild() {
return getParameterizedJobMixIn().scheduleBuild();
}
@@ -800,6 +813,7 @@ public abstract class AbstractProject
,R extends A
* @deprecated
* Use {@link #scheduleBuild(int, Cause)}. Since 1.283
*/
+ @Deprecated
public boolean scheduleBuild(int quietPeriod) {
return getParameterizedJobMixIn().scheduleBuild(quietPeriod);
}
@@ -1221,6 +1235,7 @@ public abstract class AbstractProject
,R extends A
* If you need to lock a workspace while you do some computation, see the source code of
* {@link #pollSCMChanges(TaskListener)} for how to obtain a lock of a workspace through {@link WorkspaceList}.
*/
+ @Deprecated
public Resource getWorkspaceResource() {
return new Resource(getFullDisplayName()+" workspace");
}
@@ -1294,6 +1309,7 @@ public abstract class AbstractProject
,R extends A
* @deprecated as of 1.346
* Use {@link #poll(TaskListener)} instead.
*/
+ @Deprecated
public boolean pollSCMChanges( TaskListener listener ) {
return poll(listener).hasChanges();
}
@@ -1451,9 +1467,9 @@ public abstract class AbstractProject
,R extends A
// OTOH, if a concurrent build is chosen, the user is willing to create a multiple workspace,
// so better throughput is achieved over time (modulo the initial cost of creating that many workspaces)
// by having multiple workspaces
- WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
Node node = lb.getBuiltOn();
Launcher launcher = ws.createLauncher(listener).decorateByEnv(getEnvironment(node,listener));
+ WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
try {
listener.getLogger().println("Polling SCM changes on " + node.getSelfLabel().getName());
LOGGER.fine("Polling SCM changes of " + getName());
@@ -1751,6 +1767,7 @@ public abstract class AbstractProject
,R extends A
* @deprecated as of 1.489
* Inject {@link TimeDuration}.
*/
+ @Deprecated
public int getDelay(StaplerRequest req) throws ServletException {
String delay = req.getParameter("delay");
if (delay==null) return getQuietPeriod();
@@ -1867,6 +1884,7 @@ public abstract class AbstractProject
,R extends A
* @deprecated
* As of 1.261. Use {@link #buildDescribable(StaplerRequest, List)} instead.
*/
+ @Deprecated
protected final > List buildDescribable(StaplerRequest req, List extends Descriptor> descriptors, String prefix) throws FormException, ServletException {
return buildDescribable(req,descriptors);
}
@@ -2214,6 +2232,7 @@ public abstract class AbstractProject
,R extends A
/**
* @deprecated Just use {@link #CANCEL}.
*/
+ @Deprecated
public static final Permission ABORT = CANCEL;
/**
diff --git a/core/src/main/java/hudson/model/Actionable.java b/core/src/main/java/hudson/model/Actionable.java
index ba19d39257..8285544821 100644
--- a/core/src/main/java/hudson/model/Actionable.java
+++ b/core/src/main/java/hudson/model/Actionable.java
@@ -30,6 +30,9 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
import jenkins.model.ModelObjectWithContextMenu;
import jenkins.model.TransientActionFactory;
import org.kohsuke.stapler.StaplerRequest;
@@ -91,7 +94,11 @@ public abstract class Actionable extends AbstractModelObject implements ModelObj
List _actions = new ArrayList(getActions());
for (TransientActionFactory> taf : ExtensionList.lookup(TransientActionFactory.class)) {
if (taf.type().isInstance(this)) {
- _actions.addAll(createFor(taf));
+ try {
+ _actions.addAll(createFor(taf));
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE, "Could not load actions from " + taf + " for " + this, e);
+ }
}
}
return Collections.unmodifiableList(_actions);
@@ -177,4 +184,6 @@ public abstract class Actionable extends AbstractModelObject implements ModelObj
@Override public ContextMenu doContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
return new ContextMenu().from(this,request,response);
}
+
+ private static final Logger LOGGER = Logger.getLogger(Actionable.class.getName());
}
diff --git a/core/src/main/java/hudson/model/Api.java b/core/src/main/java/hudson/model/Api.java
index c749f46d13..258f48ffea 100644
--- a/core/src/main/java/hudson/model/Api.java
+++ b/core/src/main/java/hudson/model/Api.java
@@ -24,6 +24,7 @@
package hudson.model;
import hudson.ExtensionList;
+import jenkins.util.xml.FilteredFunctionContext;
import jenkins.model.Jenkins;
import jenkins.security.SecureRequester;
@@ -32,6 +33,7 @@ import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
+import org.dom4j.XPath;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.kohsuke.stapler.QueryParameter;
@@ -107,14 +109,16 @@ public class Api extends AbstractModelObject {
p.writeTo(bean,pruner,Flavor.XML.createDataWriter(bean,sw));
// apply XPath
+ FilteredFunctionContext functionContext = new FilteredFunctionContext();
Object result;
try {
Document dom = new SAXReader().read(new StringReader(sw.toString()));
-
// apply exclusions
if (excludes!=null) {
for (String exclude : excludes) {
- List list = (List)dom.selectNodes(exclude);
+ XPath xExclude = dom.createXPath(exclude);
+ xExclude.setFunctionContext(functionContext);
+ List list = (List)xExclude.selectNodes(dom);
for (org.dom4j.Node n : list) {
Element parent = n.getParent();
if(parent!=null)
@@ -126,7 +130,9 @@ public class Api extends AbstractModelObject {
if(xpath==null) {
result = dom;
} else {
- List list = dom.selectNodes(xpath);
+ XPath comp = dom.createXPath(xpath);
+ comp.setFunctionContext(functionContext);
+ List list = comp.selectNodes(dom);
if (wrapper!=null) {
Element root = DocumentFactory.getInstance().createElement(wrapper);
for (Object o : list) {
@@ -202,7 +208,7 @@ public class Api extends AbstractModelObject {
public void doJson(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
if (req.getParameter("jsonp") == null || permit(req)) {
setHeaders(rsp);
- rsp.serveExposedBean(req,bean, Flavor.JSON);
+ rsp.serveExposedBean(req,bean, req.getParameter("jsonp") == null ? Flavor.JSON : Flavor.JSONP);
} else {
rsp.sendError(HttpURLConnection.HTTP_FORBIDDEN, "jsonp forbidden; implement jenkins.security.SecureRequester");
}
diff --git a/core/src/main/java/hudson/model/Build.java b/core/src/main/java/hudson/model/Build.java
index 99073f1b73..a5dbcfcf1f 100644
--- a/core/src/main/java/hudson/model/Build.java
+++ b/core/src/main/java/hudson/model/Build.java
@@ -119,6 +119,7 @@ public abstract class Build
,B extends Build
>
* proper execution object.
*/
@Restricted(NoExternalUse.class)
+ @Deprecated
protected Runner createRunner() {
return new BuildExecution();
}
@@ -127,6 +128,7 @@ public abstract class Build
,B extends Build
>
* @deprecated as of 1.467
* Please use {@link BuildExecution}
*/
+ @Deprecated
protected class RunnerImpl extends BuildExecution {
}
@@ -189,8 +191,12 @@ public abstract class Build
,B extends Build
>
@Override
public void cleanUp(@Nonnull BuildListener listener) throws Exception {
// at this point it's too late to mark the build as a failure, so ignore return value.
- performAllBuildSteps(listener, project.getPublishersList(), false);
- performAllBuildSteps(listener, project.getProperties(), false);
+ try {
+ performAllBuildSteps(listener, project.getPublishersList(), false);
+ performAllBuildSteps(listener, project.getProperties(), false);
+ } catch (Exception x) {
+ x.printStackTrace(listener.error(Messages.Build_post_build_steps_failed()));
+ }
super.cleanUp(listener);
}
diff --git a/core/src/main/java/hudson/model/BuildAuthorizationToken.java b/core/src/main/java/hudson/model/BuildAuthorizationToken.java
index 7ab272afda..8aa53eb429 100644
--- a/core/src/main/java/hudson/model/BuildAuthorizationToken.java
+++ b/core/src/main/java/hudson/model/BuildAuthorizationToken.java
@@ -45,6 +45,7 @@ import org.kohsuke.stapler.HttpResponses;
* Use {@link ACL} and {@link Item#BUILD}. This code is only here
* for the backward compatibility.
*/
+@Deprecated
public final class BuildAuthorizationToken {
private final String token;
diff --git a/core/src/main/java/hudson/model/BuildableItem.java b/core/src/main/java/hudson/model/BuildableItem.java
index 17956d175b..ef845e1175 100644
--- a/core/src/main/java/hudson/model/BuildableItem.java
+++ b/core/src/main/java/hudson/model/BuildableItem.java
@@ -39,12 +39,14 @@ public interface BuildableItem extends Item, Task {
* @deprecated
* Use {@link #scheduleBuild(Cause)}. Since 1.283
*/
+ @Deprecated
boolean scheduleBuild();
boolean scheduleBuild(Cause c);
/**
* @deprecated
* Use {@link #scheduleBuild(int, Cause)}. Since 1.283
*/
+ @Deprecated
boolean scheduleBuild(int quietPeriod);
boolean scheduleBuild(int quietPeriod, Cause c);
}
diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java
index 37dbcee4ca..2ee6ba7982 100644
--- a/core/src/main/java/hudson/model/Cause.java
+++ b/core/src/main/java/hudson/model/Cause.java
@@ -124,6 +124,7 @@ public abstract class Cause {
* Fall back implementation when no other type is available.
* @deprecated since 2009-02-08
*/
+ @Deprecated
public static class LegacyCodeCause extends Cause {
private StackTraceElement [] stackTrace;
public LegacyCodeCause() {
@@ -162,6 +163,7 @@ public abstract class Cause {
* @deprecated since 2009-02-28
*/
// for backward bytecode compatibility
+ @Deprecated
public UpstreamCause(AbstractBuild,?> up) {
this((Run,?>)up);
}
@@ -361,6 +363,7 @@ public abstract class Cause {
* @deprecated 1.428
* use {@link UserIdCause}
*/
+ @Deprecated
public static class UserCause extends Cause {
private String authenticationName;
public UserCause() {
diff --git a/core/src/main/java/hudson/model/CauseAction.java b/core/src/main/java/hudson/model/CauseAction.java
index afe715cef0..252c91c722 100644
--- a/core/src/main/java/hudson/model/CauseAction.java
+++ b/core/src/main/java/hudson/model/CauseAction.java
@@ -113,6 +113,7 @@ public class CauseAction implements FoldableAction, RunAction2 {
* @deprecated as of 1.288
* but left here for backward compatibility.
*/
+ @Deprecated
public String getShortDescription() {
if(causes.isEmpty()) return "N/A";
return causes.get(0).getShortDescription();
diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java
index 2d23453fad..1f9f94a721 100644
--- a/core/src/main/java/hudson/model/Computer.java
+++ b/core/src/main/java/hudson/model/Computer.java
@@ -45,6 +45,7 @@ import hudson.security.AccessControlled;
import hudson.security.Permission;
import hudson.security.PermissionGroup;
import hudson.security.PermissionScope;
+import hudson.slaves.AbstractCloudSlave;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.ComputerListener;
import hudson.slaves.NodeProperty;
@@ -61,10 +62,14 @@ import hudson.util.RunList;
import hudson.util.Futures;
import hudson.util.NamingThreadFactory;
import jenkins.model.Jenkins;
+import jenkins.model.queue.AsynchronousExecution;
import jenkins.util.ContextResettingExecutorService;
import jenkins.security.MasterToSlaveCallable;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.QueryParameter;
@@ -77,6 +82,7 @@ import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.args4j.Option;
import org.kohsuke.stapler.interceptor.RequirePOST;
+import javax.annotation.concurrent.GuardedBy;
import javax.servlet.ServletException;
import java.io.File;
import java.io.FilenameFilter;
@@ -164,12 +170,70 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
private volatile String cachedHostName;
private volatile boolean hostNameCached;
+ /**
+ * @see #getEnvironment()
+ */
+ private volatile EnvVars cachedEnvironment;
+
+
private final WorkspaceList workspaceList = new WorkspaceList();
protected transient List transientActions;
protected final Object statusChangeLock = new Object();
+ /**
+ * Keeps track of stack traces to track the tremination requests for this computer.
+ *
+ * @since 1.607
+ * @see Executor#resetWorkUnit(String)
+ */
+ private transient final List terminatedBy = Collections.synchronizedList(new ArrayList
+ ());
+
+ /**
+ * This method captures the information of a request to terminate a computer instance. Method is public as
+ * it needs to be called from {@link AbstractCloudSlave} and {@link jenkins.model.Nodes}. In general you should
+ * not need to call this method directly, however if implementing a custom node type or a different path
+ * for removing nodes, it may make sense to call this method in order to capture the originating request.
+ *
+ * @since 1.607
+ */
+ public void recordTermination() {
+ StaplerRequest request = Stapler.getCurrentRequest();
+ if (request != null) {
+ terminatedBy.add(new TerminationRequest(
+ String.format("Termination requested at %s by %s [id=%d] from HTTP request for %s",
+ new Date(),
+ Thread.currentThread(),
+ Thread.currentThread().getId(),
+ request.getRequestURL()
+ )
+ ));
+ } else {
+ terminatedBy.add(new TerminationRequest(
+ String.format("Termination requested at %s by %s [id=%d]",
+ new Date(),
+ Thread.currentThread(),
+ Thread.currentThread().getId()
+ )
+ ));
+ }
+ }
+
+ /**
+ * Returns the list of captured termination requests for this Computer. This method is used by {@link Executor}
+ * to provide details on why a Computer was removed in-between work being scheduled against the {@link Executor}
+ * and the {@link Executor} starting to execute the task.
+ *
+ * @return the (possibly empty) list of termination requests.
+ * @see Executor#resetWorkUnit(String)
+ * @since 1.607
+ */
+ public List getTerminatedBy() {
+ return new ArrayList(terminatedBy);
+ }
+
public Computer(Node node) {
setNode(node);
}
@@ -207,14 +271,25 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
/**
* This is where the log from the remote agent goes.
* The method also creates a log directory if required.
- * @see #relocateOldLogs()
+ * @see #getLogDir(), #relocateOldLogs()
*/
- public File getLogFile() {
+ public @Nonnull File getLogFile() {
+ return new File(getLogDir(),"slave.log");
+ }
+
+ /**
+ * Directory where rotated slave logs are stored.
+ *
+ * The method also creates a log directory if required.
+ *
+ * @since 1.613
+ */
+ protected @Nonnull File getLogDir() {
File dir = new File(Jenkins.getInstance().getRootDir(),"logs/slaves/"+nodeName);
if (!dir.exists() && !dir.mkdirs()) {
LOGGER.severe("Failed to create slave log directory " + dir.getAbsolutePath());
}
- return new File(dir,"slave.log");
+ return dir;
}
/**
@@ -314,6 +389,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
/**
* @deprecated since 2009-01-06. Use {@link #connect(boolean)}
*/
+ @Deprecated
public final void launch() {
connect(true);
}
@@ -339,6 +415,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* make much sense because as soon as {@link Computer} is connected it can be disconnected by some other threads.)
*/
public final Future> connect(boolean forceReconnect) {
+ this.cachedEnvironment = null;
connectTime = System.currentTimeMillis();
return _connect(forceReconnect);
}
@@ -397,6 +474,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* @since 1.320
*/
public Future> disconnect(OfflineCause cause) {
+ recordTermination();
offlineCause = cause;
if (Util.isOverridden(Computer.class,getClass(),"disconnect"))
return disconnect(); // legacy subtypes that extend disconnect().
@@ -411,7 +489,9 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* @deprecated as of 1.320.
* Use {@link #disconnect(OfflineCause)} and specify the cause.
*/
+ @Deprecated
public Future> disconnect() {
+ recordTermination();
if (Util.isOverridden(Computer.class,getClass(),"disconnect",OfflineCause.class))
// if the subtype already derives disconnect(OfflineCause), delegate to it
return disconnect(null);
@@ -435,7 +515,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
@CLIMethod(name="offline-node")
public void cliOffline(@Option(name="-m",usage="Record the note about why you are disconnecting this node") String cause) throws ExecutionException, InterruptedException {
checkPermission(DISCONNECT);
- setTemporarilyOffline(true,new ByCLI(cause));
+ setTemporarilyOffline(true, new ByCLI(cause));
}
@CLIMethod(name="online-node")
@@ -573,6 +653,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* accidentally call this method.
*/
@Exported
+ @Deprecated
public boolean isTemporarilyOffline() {
return temporarilyOffline;
}
@@ -581,6 +662,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* @deprecated as of 1.320.
* Use {@link #setTemporarilyOffline(boolean, OfflineCause)}
*/
+ @Deprecated
public void setTemporarilyOffline(boolean temporarilyOffline) {
setTemporarilyOffline(temporarilyOffline,null);
}
@@ -691,6 +773,26 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* @see #onRemoved()
*/
protected void kill() {
+ // On most code paths, this should already be zero, and thus this next call becomes a no-op... and more
+ // importantly it will not acquire a lock on the Queue... not that the lock is bad, more that the lock
+ // may delay unnecessarily
+ setNumExecutors(0);
+ }
+
+ /**
+ * Called by {@link Jenkins#updateComputerList()} to notify {@link Computer} that it will be discarded.
+ *
+ *
+ * Note that at this point {@link #getNode()} returns null.
+ *
+ *
+ * Note that the Queue lock is already held when this method is called.
+ *
+ * @see #onRemoved()
+ */
+ @Restricted(NoExternalUse.class)
+ @GuardedBy("hudson.model.Queue.lock")
+ /*package*/ void inflictMortalWound() {
setNumExecutors(0);
}
@@ -801,6 +903,32 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
return new ArrayList(oneOffExecutors);
}
+ /**
+ * Used to render the list of executors.
+ * @return a snapshot of the executor display information
+ * @since 1.607
+ */
+ @Restricted(NoExternalUse.class)
+ public List getDisplayExecutors() {
+ // The size may change while we are populating, but let's start with a reasonable guess to minimize resizing
+ List result = new ArrayList(executors.size()+oneOffExecutors.size());
+ int index = 0;
+ for (Executor e: executors) {
+ if (e.isDisplayCell()) {
+ result.add(new DisplayExecutor(Integer.toString(index + 1), String.format("executors/%d", index), e));
+ }
+ index++;
+ }
+ index = 0;
+ for (OneOffExecutor e: oneOffExecutors) {
+ if (e.isDisplayCell()) {
+ result.add(new DisplayExecutor("", String.format("oneOffExecutors/%d", index), e));
+ }
+ index++;
+ }
+ return result;
+ }
+
/**
* Returns true if all the executors of this computer are idle.
*/
@@ -860,18 +988,25 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
/**
* Called by {@link Executor} to kill excessive executors from this computer.
*/
- /*package*/ synchronized void removeExecutor(Executor e) {
- executors.remove(e);
- addNewExecutorIfNecessary();
- if(!isAlive())
- {
- AbstractCIBase ciBase = Jenkins.getInstance();
- ciBase.removeComputer(this);
- }
+ /*package*/ void removeExecutor(final Executor e) {
+ Queue.withLock(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (Computer.this) {
+ executors.remove(e);
+ addNewExecutorIfNecessary();
+ if (!isAlive()) // TODO except from interrupt/doYank this is called while the executor still isActive(), so how could !this.isAlive()?
+ {
+ AbstractCIBase ciBase = Jenkins.getInstance();
+ ciBase.removeComputer(Computer.this);
+ }
+ }
+ }
+ });
}
/**
- * Returns true if any of the executors are functioning.
+ * Returns true if any of the executors are {@linkplain Executor#isActive active}.
*
* Note that if an executor dies, we'll leave it in {@link #executors} until
* the administrator yanks it out, so that we can see why it died.
@@ -887,10 +1022,11 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
/**
* Interrupt all {@link Executor}s.
+ * Called from {@link Jenkins#cleanUp}.
*/
public void interrupt() {
for (Executor e : executors) {
- e.interrupt();
+ e.interruptForShutdown();
}
}
@@ -930,16 +1066,24 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* @deprecated as of 1.292
* Use {@link #getEnvironment()} instead.
*/
+ @Deprecated
public Map getEnvVars() throws IOException, InterruptedException {
return getEnvironment();
}
/**
- * Gets the environment variables of the JVM on this computer.
+ * Returns cached environment variables (copy to prevent modification) for the JVM on this computer.
* If this is the master, it returns the system property of the master computer.
*/
public EnvVars getEnvironment() throws IOException, InterruptedException {
- return EnvVars.getRemote(getChannel());
+ EnvVars cachedEnvironment = this.cachedEnvironment;
+ if (cachedEnvironment != null) {
+ return new EnvVars(cachedEnvironment);
+ }
+
+ cachedEnvironment = EnvVars.getRemote(getChannel());
+ this.cachedEnvironment = cachedEnvironment;
+ return new EnvVars(cachedEnvironment);
}
/**
@@ -1022,11 +1166,11 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
try {
InetAddress ia = InetAddress.getByName(address);
if(!(ia instanceof Inet4Address)) {
- LOGGER.fine(address+" is not an IPv4 address");
+ LOGGER.log(Level.FINE, "{0} is not an IPv4 address", address);
continue;
}
if(!ComputerPinger.checkIsReachable(ia, 3)) {
- LOGGER.fine(address+" didn't respond to ping");
+ LOGGER.log(Level.FINE, "{0} didn't respond to ping", address);
continue;
}
cachedHostName = ia.getCanonicalHostName();
@@ -1034,7 +1178,10 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
return cachedHostName;
} catch (IOException e) {
// if a given name fails to parse on this host, we get this error
- LOGGER.log(Level.FINE, "Failed to parse "+address,e);
+ LogRecord lr = new LogRecord(Level.FINE, "Failed to parse {0}");
+ lr.setThrown(e);
+ lr.setParameters(new Object[]{address});
+ LOGGER.log(lr);
}
}
@@ -1058,27 +1205,38 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
private static class ListPossibleNames extends MasterToSlaveCallable,IOException> {
+ /**
+ * In the normal case we would use {@link Computer} as the logger's name, however to
+ * do that we would have to send the {@link Computer} class over to the remote classloader
+ * and then it would need to be loaded, which pulls in {@link Jenkins} and loads that
+ * and then that fails to load as you are not supposed to do that. Another option
+ * would be to export the logger over remoting, with increased complexity as a result.
+ * Instead we just use a loger based on this class name and prevent any references to
+ * other classes from being transferred over remoting.
+ */
+ private static final Logger LOGGER = Logger.getLogger(ListPossibleNames.class.getName());
+
public List call() throws IOException {
List names = new ArrayList();
Enumeration nis = NetworkInterface.getNetworkInterfaces();
while (nis.hasMoreElements()) {
NetworkInterface ni = nis.nextElement();
- LOGGER.fine("Listing up IP addresses for "+ni.getDisplayName());
+ LOGGER.log(Level.FINE, "Listing up IP addresses for {0}", ni.getDisplayName());
Enumeration e = ni.getInetAddresses();
while (e.hasMoreElements()) {
InetAddress ia = e.nextElement();
if(ia.isLoopbackAddress()) {
- LOGGER.fine(ia+" is a loopback address");
+ LOGGER.log(Level.FINE, "{0} is a loopback address", ia);
continue;
}
if(!(ia instanceof Inet4Address)) {
- LOGGER.fine(ia+" is not an IPv4 address");
+ LOGGER.log(Level.FINE, "{0} is not an IPv4 address", ia);
continue;
}
- LOGGER.fine(ia+" is a viable candidate");
+ LOGGER.log(Level.FINE, "{0} is a viable candidate", ia);
names.add(ia.getHostAddress());
}
}
@@ -1107,12 +1265,13 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
public void doRssAll( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
rss(req, rsp, " all builds", getBuilds());
}
- public void doRssFailed( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
+
+ public void doRssFailed(StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
rss(req, rsp, " failed builds", getBuilds().failureOnly());
}
private void rss(StaplerRequest req, StaplerResponse rsp, String suffix, RunList runs) throws IOException, ServletException {
- RSS.forwardToRss(getDisplayName()+ suffix, getUrl(),
- runs.newBuilds(), Run.FEED_ADAPTER, req, rsp );
+ RSS.forwardToRss(getDisplayName() + suffix, getUrl(),
+ runs.newBuilds(), Run.FEED_ADAPTER, req, rsp);
}
@RequirePOST
@@ -1190,7 +1349,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
_doScript(req, rsp, "_scriptText.jelly");
}
- protected void _doScript( StaplerRequest req, StaplerResponse rsp, String view) throws IOException, ServletException {
+ protected void _doScript(StaplerRequest req, StaplerResponse rsp, String view) throws IOException, ServletException {
Jenkins._doScript(req, rsp, req.getView(this, view), getChannel(), getACL());
}
@@ -1314,7 +1473,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* Handles incremental log.
*/
public void doProgressiveLog( StaplerRequest req, StaplerResponse rsp) throws IOException {
- getLogText().doProgressText(req,rsp);
+ getLogText().doProgressText(req, rsp);
}
/**
@@ -1399,6 +1558,101 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
}
+ /**
+ * A value class to provide a consistent snapshot view of the state of an executor to avoid race conditions
+ * during rendering of the executors list.
+ *
+ * @since 1.607
+ */
+ @Restricted(NoExternalUse.class)
+ public static class DisplayExecutor implements ModelObject {
+
+ @Nonnull
+ private final String displayName;
+ @Nonnull
+ private final String url;
+ @Nonnull
+ private final Executor executor;
+
+ public DisplayExecutor(@Nonnull String displayName, @Nonnull String url, @Nonnull Executor executor) {
+ this.displayName = displayName;
+ this.url = url;
+ this.executor = executor;
+ }
+
+ @Override
+ @Nonnull
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ @Nonnull
+ public String getUrl() {
+ return url;
+ }
+
+ @Nonnull
+ public Executor getExecutor() {
+ return executor;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("DisplayExecutor{");
+ sb.append("displayName='").append(displayName).append('\'');
+ sb.append(", url='").append(url).append('\'');
+ sb.append(", executor=").append(executor);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ DisplayExecutor that = (DisplayExecutor) o;
+
+ if (!executor.equals(that.executor)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return executor.hashCode();
+ }
+ }
+
+ /**
+ * Used to trace requests to terminate a computer.
+ *
+ * @since 1.607
+ */
+ public static class TerminationRequest extends RuntimeException {
+ private final long when;
+ public TerminationRequest(String message) {
+ super(message);
+ this.when = System.currentTimeMillis();
+ }
+
+ /**
+ * Returns the when the termination request was created.
+ *
+ * @return the difference, measured in milliseconds, between
+ * the time of the termination request and midnight, January 1, 1970 UTC.
+ */
+ public long getWhen() {
+ return when;
+ }
+ }
+
public static final PermissionGroup PERMISSIONS = new PermissionGroup(Computer.class,Messages._Computer_Permissions_Title());
public static final Permission CONFIGURE = new Permission(PERMISSIONS,"Configure", Messages._Computer_ConfigurePermission_Description(), Permission.CONFIGURE, PermissionScope.COMPUTER);
/**
diff --git a/core/src/main/java/hudson/model/ComputerSet.java b/core/src/main/java/hudson/model/ComputerSet.java
index 0502c64329..40b71fb24e 100644
--- a/core/src/main/java/hudson/model/ComputerSet.java
+++ b/core/src/main/java/hudson/model/ComputerSet.java
@@ -95,6 +95,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
* @deprecated as of 1.301
* Use {@link #getMonitors()}.
*/
+ @Deprecated
public static List get_monitors() {
return monitors.toList();
}
@@ -290,6 +291,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
JSONObject formData = req.getSubmittedForm();
formData.put("name", fixedName);
+ // TODO type is probably NodeDescriptor.id but confirm
Node result = NodeDescriptor.all().find(type).newInstance(req, formData);
app.addNode(result);
diff --git a/core/src/main/java/hudson/model/DependecyDeclarer.java b/core/src/main/java/hudson/model/DependecyDeclarer.java
index 2693515053..5cd1fe6400 100644
--- a/core/src/main/java/hudson/model/DependecyDeclarer.java
+++ b/core/src/main/java/hudson/model/DependecyDeclarer.java
@@ -30,4 +30,5 @@ import jenkins.model.DependencyDeclarer;
* @deprecated Use {@link DependencyDeclarer} instead.
* @since 1.160
*/
+@Deprecated
public interface DependecyDeclarer extends DependencyDeclarer {}
diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java
index bb6eeb2126..4c5693b933 100644
--- a/core/src/main/java/hudson/model/Descriptor.java
+++ b/core/src/main/java/hudson/model/Descriptor.java
@@ -366,6 +366,7 @@ public abstract class Descriptor> implements Saveable {
* @deprecated since 1.528
* Use {@link #getCheckMethod(String)}
*/
+ @Deprecated
public String getCheckUrl(String fieldName) {
return getCheckMethod(fieldName).toCheckUrl();
}
@@ -512,6 +513,7 @@ public abstract class Descriptor> implements Saveable {
* Implement {@link #newInstance(StaplerRequest, JSONObject)} method instead.
* Deprecated as of 1.145.
*/
+ @Deprecated
public T newInstance(StaplerRequest req) throws FormException {
throw new UnsupportedOperationException(getClass()+" should implement newInstance(StaplerRequest,JSONObject)");
}
@@ -684,6 +686,7 @@ public abstract class Descriptor> implements Saveable {
* @deprecated
* As of 1.239, use {@link #configure(StaplerRequest, JSONObject)}.
*/
+ @Deprecated
public boolean configure( StaplerRequest req ) throws FormException {
return true;
}
@@ -909,10 +912,35 @@ public abstract class Descriptor> implements Saveable {
if (formData!=null) {
for (Object o : JSONArray.fromObject(formData)) {
JSONObject jo = (JSONObject)o;
- String kind = jo.getString("kind");
- Descriptor d = find(descriptors, kind);
+ Descriptor d = null;
+ // 'kind' and '$class' are mutually exclusive (see class-entry.jelly), but to be more lenient on the reader side,
+ // we check them both anyway. 'kind' (which maps to ID) is more unique than '$class', which can have multiple matching
+ // Descriptors, so we prefer 'kind' if it's present.
+ String kind = jo.optString("kind", null);
+ if (kind != null) {
+ // Only applies when Descriptor.getId is overridden.
+ // Note that kind is only supported here,
+ // *not* inside the StaplerRequest.bindJSON which is normally called by newInstance
+ // (since Descriptor.newInstance is not itself available to Stapler).
+ // If you merely override getId for some reason, but use @DataBoundConstructor on your Describable,
+ // there is no problem; but you can only rely on newInstance being called at top level.
+ d = findById(descriptors, kind);
+ }
+ if (d == null) {
+ kind = jo.optString("$class");
+ if (kind != null) { // else we will fall through to the warning
+ // This is the normal case.
+ d = findByDescribableClassName(descriptors, kind);
+ if (d == null) {
+ // Deprecated system where stapler-class was the Descriptor class name (rather than Describable class name).
+ d = findByClassName(descriptors, kind);
+ }
+ }
+ }
if (d != null) {
items.add(d.newInstance(req, jo));
+ } else {
+ LOGGER.log(Level.WARNING, "Received unexpected form data element: {0}", jo);
}
}
}
@@ -921,22 +949,58 @@ public abstract class Descriptor> implements Saveable {
}
/**
- * Finds a descriptor from a collection by its class name.
+ * Finds a descriptor from a collection by its ID.
+ * @param id should match {@link #getId}
+ * @since 1.610
*/
- public static @CheckForNull T find(Collection extends T> list, String className) {
+ public static @CheckForNull T findById(Collection extends T> list, String id) {
for (T d : list) {
- if(d.getClass().getName().equals(className))
- return d;
- }
- // Since we introduced Descriptor.getId(), it is a preferred method of identifying descriptor by a string.
- // To make that migration easier without breaking compatibility, let's also match up with the id.
- for (T d : list) {
- if(d.getId().equals(className))
+ if(d.getId().equals(id))
return d;
}
return null;
}
+ /**
+ * Finds a descriptor from a collection by the class name of the {@link Descriptor}.
+ * This is useless as of the introduction of {@link #getId} and so only very old compatibility code needs it.
+ */
+ private static @CheckForNull T findByClassName(Collection extends T> list, String className) {
+ for (T d : list) {
+ if(d.getClass().getName().equals(className))
+ return d;
+ }
+ return null;
+ }
+
+ /**
+ * Finds a descriptor from a collection by the class name of the {@link Describable} it describes.
+ * @param className should match {@link Class#getName} of a {@link #clazz}
+ * @since 1.610
+ */
+ public static @CheckForNull T findByDescribableClassName(Collection extends T> list, String className) {
+ for (T d : list) {
+ if(d.clazz.getName().equals(className))
+ return d;
+ }
+ return null;
+ }
+
+ /**
+ * Finds a descriptor from a collection by its class name or ID.
+ * @deprecated choose between {@link #findById} or {@link #findByDescribableClassName}
+ */
+ public static @CheckForNull T find(Collection extends T> list, String string) {
+ T d = findByClassName(list, string);
+ if (d != null) {
+ return d;
+ }
+ return findById(list, string);
+ }
+
+ /**
+ * @deprecated choose between {@link #findById} or {@link #findByDescribableClassName}
+ */
public static @CheckForNull Descriptor find(String className) {
return find(ExtensionList.lookup(Descriptor.class),className);
}
diff --git a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
index cf803fcdc5..16dacc90a2 100644
--- a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
+++ b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
@@ -75,6 +75,7 @@ public final class DirectoryBrowserSupport implements HttpResponse {
* @deprecated as of 1.297
* Use {@link #DirectoryBrowserSupport(ModelObject, FilePath, String, String, boolean)}
*/
+ @Deprecated
public DirectoryBrowserSupport(ModelObject owner, String title) {
this(owner, (VirtualFile) null, title, null, false);
}
@@ -147,6 +148,7 @@ public final class DirectoryBrowserSupport implements HttpResponse {
* Instead of calling this method explicitly, just return the {@link DirectoryBrowserSupport} object
* from the {@code doXYZ} method and let Stapler generate a response for you.
*/
+ @Deprecated
public void serveFile(StaplerRequest req, StaplerResponse rsp, FilePath root, String icon, boolean serveDirIndex) throws IOException, ServletException, InterruptedException {
serveFile(req, rsp, root.toVirtualFile(), icon, serveDirIndex);
}
diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java
index 707491dbc1..ff5e3811c9 100644
--- a/core/src/main/java/hudson/model/DownloadService.java
+++ b/core/src/main/java/hudson/model/DownloadService.java
@@ -66,7 +66,7 @@ public class DownloadService extends PageDecorator {
* Builds up an HTML fragment that starts all the download jobs.
*/
public String generateFragment() {
- if (!DownloadSettings.get().isUseBrowser()) {
+ if (!DownloadSettings.usePostBack()) {
return "";
}
if (neverUpdate) return "";
@@ -170,6 +170,30 @@ public class DownloadService extends PageDecorator {
}
}
+ /**
+ * Loads JSON from a JSON-with-{@code postMessage} URL.
+ * @param src a URL to a JSON HTML file (typically including {@code id} and {@code version} query parameters)
+ * @return the embedded JSON text
+ * @throws IOException if either downloading or processing failed
+ */
+ @Restricted(NoExternalUse.class)
+ public static String loadJSONHTML(URL src) throws IOException {
+ InputStream is = ProxyConfiguration.open(src).getInputStream();
+ try {
+ String jsonp = IOUtils.toString(is, "UTF-8");
+ String preamble = "window.parent.postMessage(JSON.stringify(";
+ int start = jsonp.indexOf(preamble);
+ int end = jsonp.lastIndexOf("),'*');");
+ if (start >= 0 && end > start) {
+ return jsonp.substring(start + preamble.length(), end).trim();
+ } else {
+ throw new IOException("Could not find JSON in " + src);
+ }
+ } finally {
+ is.close();
+ }
+ }
+
/**
* Represents a periodically updated JSON data file obtained from a remote URL.
*
@@ -283,9 +307,7 @@ public class DownloadService extends PageDecorator {
* This is where the browser sends us the data.
*/
public void doPostBack(StaplerRequest req, StaplerResponse rsp) throws IOException {
- if (!DownloadSettings.get().isUseBrowser()) {
- throw new IOException("not allowed");
- }
+ DownloadSettings.checkPostBackAccess();
long dataTimestamp = System.currentTimeMillis();
due = dataTimestamp+getInterval(); // success or fail, don't try too often
@@ -317,7 +339,7 @@ public class DownloadService extends PageDecorator {
@Restricted(NoExternalUse.class)
public FormValidation updateNow() throws IOException {
- return load(loadJSON(new URL(getUrl() + "?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), System.currentTimeMillis());
+ return load(loadJSONHTML(new URL(getUrl() + ".html?id=" + URLEncoder.encode(getId(), "UTF-8") + "&version=" + URLEncoder.encode(Jenkins.VERSION, "UTF-8"))), System.currentTimeMillis());
}
/**
diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java
index 574cfb7486..08864961eb 100644
--- a/core/src/main/java/hudson/model/Executor.java
+++ b/core/src/main/java/hudson/model/Executor.java
@@ -46,13 +46,18 @@ import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
+import javax.annotation.concurrent.GuardedBy;
import javax.servlet.ServletException;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -60,20 +65,24 @@ import static hudson.model.queue.Executables.*;
import static java.util.logging.Level.*;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
+import jenkins.model.queue.AsynchronousExecution;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Thread that executes builds.
* Since 1.536, {@link Executor}s start threads on-demand.
- * The entire logic should use {@link #isActive()} instead of {@link #isAlive()}
- * in order to check if the {@link Executor} it ready to take tasks.
+ *
Callers should use {@link #isActive()} instead of {@link #isAlive()}.
* @author Kohsuke Kawaguchi
*/
@ExportedBean
public class Executor extends Thread implements ModelObject {
protected final @Nonnull Computer owner;
private final Queue queue;
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+ @GuardedBy("lock")
private long startTime;
/**
* Used to track when a job was last executed.
@@ -87,29 +96,41 @@ public class Executor extends Thread implements ModelObject {
/**
* {@link hudson.model.Queue.Executable} being executed right now, or null if the executor is idle.
*/
- private volatile Queue.Executable executable;
+ @GuardedBy("lock")
+ private Queue.Executable executable;
+
+ /**
+ * Used to mark that the execution is continuing asynchronously even though {@link Executor} as {@link Thread}
+ * has finished.
+ */
+ @GuardedBy("lock")
+ private AsynchronousExecution asynchronousExecution;
/**
* When {@link Queue} allocates a work for this executor, this field is set
* and the executor is {@linkplain Thread#start() started}.
*/
- private volatile WorkUnit workUnit;
+ @GuardedBy("lock")
+ private WorkUnit workUnit;
private Throwable causeOfDeath;
private boolean induceDeath;
- private volatile boolean started;
+ @GuardedBy("lock")
+ private boolean started;
/**
* When the executor is interrupted, we allow the code that interrupted the thread to override the
* result code it prefers.
*/
+ @GuardedBy("lock")
private Result interruptStatus;
/**
* Cause of interruption. Access needs to be synchronized.
*/
+ @GuardedBy("lock")
private final List causes = new Vector();
public Executor(@Nonnull Computer owner, int n) {
@@ -124,6 +145,9 @@ public class Executor extends Thread implements ModelObject {
interrupt(Result.ABORTED);
}
+ void interruptForShutdown() {
+ interrupt(Result.ABORTED, true);
+ }
/**
* Interrupt the execution,
* but instead of marking the build as aborted, mark it as specified result.
@@ -131,13 +155,17 @@ public class Executor extends Thread implements ModelObject {
* @since 1.417
*/
public void interrupt(Result result) {
+ interrupt(result, false);
+ }
+
+ private void interrupt(Result result, boolean forShutdown) {
Authentication a = Jenkins.getAuthentication();
if (a == ACL.SYSTEM)
- interrupt(result, new CauseOfInterruption[0]);
+ interrupt(result, forShutdown, new CauseOfInterruption[0]);
else {
// worth recording who did it
// avoid using User.get() to avoid deadlock.
- interrupt(result, new UserInterruption(a.getName()));
+ interrupt(result, forShutdown, new UserInterruption(a.getName()));
}
}
@@ -145,31 +173,48 @@ public class Executor extends Thread implements ModelObject {
* Interrupt the execution. Mark the cause and the status accordingly.
*/
public void interrupt(Result result, CauseOfInterruption... causes) {
+ interrupt(result, false, causes);
+ }
+
+ private void interrupt(Result result, boolean forShutdown, CauseOfInterruption... causes) {
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, String.format("%s is interrupted(%s): %s", getDisplayName(), result, Util.join(Arrays.asList(causes),",")), new InterruptedException());
- synchronized (this) {
+ lock.writeLock().lock();
+ try {
if (!started) {
// not yet started, so simply dispose this
owner.removeExecutor(this);
return;
}
- }
- interruptStatus = result;
- synchronized (this.causes) {
+ interruptStatus = result;
+
for (CauseOfInterruption c : causes) {
if (!this.causes.contains(c))
this.causes.add(c);
}
+ if (asynchronousExecution != null) {
+ asynchronousExecution.interrupt(forShutdown);
+ } else {
+ super.interrupt();
+ }
+ } finally {
+ lock.writeLock().unlock();
}
- super.interrupt();
}
public Result abortResult() {
- Result r = interruptStatus;
- if (r==null) r = Result.ABORTED; // this is when we programmatically throw InterruptedException instead of calling the interrupt method.
- return r;
+ lock.readLock().lock();
+ try {
+ Result r = interruptStatus;
+ if (r == null) r =
+ Result.ABORTED; // this is when we programmatically throw InterruptedException instead of calling the interrupt method.
+
+ return r;
+ } finally {
+ lock.readLock().unlock();
+ }
}
/**
@@ -180,11 +225,14 @@ public class Executor extends Thread implements ModelObject {
public void recordCauseOfInterruption(Run,?> build, TaskListener listener) {
List r;
- // atomically get&clear causes, and minimize the lock.
- synchronized (causes) {
+ // atomically get&clear causes.
+ lock.writeLock().lock();
+ try {
if (causes.isEmpty()) return;
r = new ArrayList(causes);
causes.clear();
+ } finally {
+ lock.writeLock().unlock();
}
build.addAction(new InterruptedBuildAction(r));
@@ -192,9 +240,64 @@ public class Executor extends Thread implements ModelObject {
c.print(listener);
}
+ /**
+ * There are some cases where an executor is started but the node is removed or goes off-line before we are ready
+ * to start executing the assigned work unit. This method is called to clear the assigned work unit so that
+ * the {@link Queue#maintain()} method can return the item to the buildable state.
+ *
+ * Note: once we create the {@link Executable} we cannot unwind the state and the build will have to end up being
+ * marked as a failure.
+ */
+ private void resetWorkUnit(String reason) {
+ StringWriter writer = new StringWriter();
+ PrintWriter pw = new PrintWriter(writer);
+ try {
+ pw.printf("%s grabbed %s from queue but %s %s. ", getName(), workUnit, owner.getDisplayName(), reason);
+ if (owner.getTerminatedBy().isEmpty()) {
+ pw.print("No termination trace available.");
+ } else {
+ pw.println("Termination trace follows:");
+ for (Computer.TerminationRequest request: owner.getTerminatedBy()) {
+ request.printStackTrace(pw);
+ }
+ }
+ } finally {
+ pw.close();
+ }
+ LOGGER.log(WARNING, writer.toString());
+ lock.writeLock().lock();
+ try {
+ if (executable != null) {
+ throw new IllegalStateException("Cannot reset the work unit after the executable has been created");
+ }
+ workUnit = null;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
@Override
public void run() {
- startTime = System.currentTimeMillis();
+ if (!owner.isOnline()) {
+ resetWorkUnit("went off-line before the task's worker thread started");
+ owner.removeExecutor(this);
+ queue.scheduleMaintenance();
+ return;
+ }
+ if (owner.getNode() == null) {
+ resetWorkUnit("was removed before the task's worker thread started");
+ owner.removeExecutor(this);
+ queue.scheduleMaintenance();
+ return;
+ }
+ final WorkUnit workUnit;
+ lock.writeLock().lock();
+ try {
+ startTime = System.currentTimeMillis();
+ workUnit = this.workUnit;
+ } finally {
+ lock.writeLock().unlock();
+ }
ACL.impersonate(ACL.SYSTEM);
@@ -204,14 +307,45 @@ public class Executor extends Thread implements ModelObject {
SubTask task;
// transition from idle to building.
// perform this state change as an atomic operation wrt other queue operations
- synchronized (queue) {
- workUnit.setExecutor(this);
- queue.onStartExecuting(this);
- if (LOGGER.isLoggable(FINE))
- LOGGER.log(FINE, getName()+" grabbed "+workUnit+" from queue");
- task = workUnit.work;
- executable = task.createExecutable();
- workUnit.setExecutable(executable);
+ task = Queue.withLock(new java.util.concurrent.Callable() {
+ @Override
+ public SubTask call() throws Exception {
+ if (!owner.isOnline()) {
+ resetWorkUnit("went off-line before the task's worker thread was ready to execute");
+ return null;
+ }
+ if (owner.getNode() == null) {
+ resetWorkUnit("was removed before the task's worker thread was ready to execute");
+ return null;
+ }
+ // after this point we cannot unwind the assignment of the work unit, if the owner
+ // is removed or goes off-line then the build will just have to fail.
+ workUnit.setExecutor(Executor.this);
+ queue.onStartExecuting(Executor.this);
+ if (LOGGER.isLoggable(FINE))
+ LOGGER.log(FINE, getName()+" grabbed "+workUnit+" from queue");
+ SubTask task = workUnit.work;
+ Executable executable = task.createExecutable();
+ lock.writeLock().lock();
+ try {
+ Executor.this.executable = executable;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ workUnit.setExecutable(executable);
+ return task;
+ }
+ });
+ Executable executable;
+ lock.readLock().lock();
+ try {
+ if (this.workUnit == null) {
+ // we called resetWorkUnit, so bail. Outer finally will remove this and schedule queue maintenance
+ return;
+ }
+ executable = this.executable;
+ } finally {
+ lock.readLock().unlock();
}
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is going to execute "+executable);
@@ -238,23 +372,26 @@ public class Executor extends Thread implements ModelObject {
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is now executing "+executable);
queue.execute(executable, task);
+ } catch (AsynchronousExecution x) {
+ lock.writeLock().lock();
+ try {
+ x.setExecutor(this);
+ this.asynchronousExecution = x;
+ } finally {
+ lock.writeLock().unlock();
+ }
} catch (Throwable e) {
- // for some reason the executor died. this is really
- // a bug in the code, but we don't want the executor to die,
- // so just leave some info and go on to build other things
- LOGGER.log(Level.SEVERE, "Executor threw an exception", e);
- workUnit.context.abort(e);
problems = e;
} finally {
- long time = System.currentTimeMillis()-startTime;
- if (LOGGER.isLoggable(FINE))
- LOGGER.log(FINE, getName()+" completed "+executable+" in "+time+"ms");
+ boolean needFinish1;
+ lock.readLock().lock();
try {
- workUnit.context.synchronizeEnd(executable,problems,time);
- } catch (InterruptedException e) {
- workUnit.context.abort(e);
+ needFinish1 = asynchronousExecution == null;
} finally {
- workUnit.setExecutor(null);
+ lock.readLock().unlock();
+ }
+ if (needFinish1) {
+ finish1(problems);
}
}
} catch (InterruptedException e) {
@@ -267,14 +404,52 @@ public class Executor extends Thread implements ModelObject {
causeOfDeath = e;
LOGGER.log(SEVERE, "Unexpected executor death", e);
} finally {
- if (causeOfDeath==null)
- // let this thread die and be replaced by a fresh unstarted instance
- owner.removeExecutor(this);
-
- queue.scheduleMaintenance();
+ if (asynchronousExecution == null) {
+ finish2();
+ }
}
}
+ private void finish1(@CheckForNull Throwable problems) {
+ if (problems != null) {
+ // for some reason the executor died. this is really
+ // a bug in the code, but we don't want the executor to die,
+ // so just leave some info and go on to build other things
+ LOGGER.log(Level.SEVERE, "Executor threw an exception", problems);
+ workUnit.context.abort(problems);
+ }
+ long time = System.currentTimeMillis() - startTime;
+ LOGGER.log(FINE, "{0} completed {1} in {2}ms", new Object[]{getName(), executable, time});
+ try {
+ workUnit.context.synchronizeEnd(this, executable, problems, time);
+ } catch (InterruptedException e) {
+ workUnit.context.abort(e);
+ } finally {
+ workUnit.setExecutor(null);
+ }
+ }
+
+ private void finish2() {
+ for (RuntimeException e1: owner.getTerminatedBy()) LOGGER.log(Level.WARNING, String.format("%s termination trace", getName()), e1);
+ if (causeOfDeath == null) {// let this thread die and be replaced by a fresh unstarted instance
+ owner.removeExecutor(this);
+ }
+ if (this instanceof OneOffExecutor) {
+ owner.remove((OneOffExecutor) this);
+ }
+ queue.scheduleMaintenance();
+ }
+
+ @Restricted(NoExternalUse.class)
+ public void completedAsynchronous(@CheckForNull Throwable error) {
+ try {
+ finish1(error);
+ } finally {
+ finish2();
+ }
+ asynchronousExecution = null;
+ }
+
/**
* For testing only. Simulate a fatal unexpected failure.
*/
@@ -290,7 +465,12 @@ public class Executor extends Thread implements ModelObject {
*/
@Exported
public @CheckForNull Queue.Executable getCurrentExecutable() {
- return executable;
+ lock.readLock().lock();
+ try {
+ return executable;
+ } finally {
+ lock.readLock().unlock();
+ }
}
/**
@@ -302,7 +482,12 @@ public class Executor extends Thread implements ModelObject {
*/
@Exported
public WorkUnit getCurrentWorkUnit() {
- return workUnit;
+ lock.readLock().lock();
+ try {
+ return workUnit;
+ } finally {
+ lock.readLock().unlock();
+ }
}
/**
@@ -311,13 +496,19 @@ public class Executor extends Thread implements ModelObject {
* to that point yet.
*/
public FilePath getCurrentWorkspace() {
- Executable e = executable;
- if(e==null) return null;
- if (e instanceof AbstractBuild) {
- AbstractBuild ab = (AbstractBuild) e;
- return ab.getWorkspace();
+ lock.readLock().lock();
+ try {
+ if (executable == null) {
+ return null;
+ }
+ if (executable instanceof AbstractBuild) {
+ AbstractBuild ab = (AbstractBuild) executable;
+ return ab.getWorkspace();
+ }
+ return null;
+ } finally {
+ lock.readLock().unlock();
}
- return null;
}
/**
@@ -344,40 +535,89 @@ public class Executor extends Thread implements ModelObject {
*/
@Exported
public boolean isIdle() {
- return executable==null;
+ lock.readLock().lock();
+ try {
+ return workUnit == null && executable == null;
+ } finally {
+ lock.readLock().unlock();
+ }
}
/**
* The opposite of {@link #isIdle()} — the executor is doing some work.
*/
public boolean isBusy() {
- return executable!=null;
+ lock.readLock().lock();
+ try {
+ return workUnit != null || executable != null;
+ } finally {
+ lock.readLock().unlock();
+ }
}
/**
* Check if executor is ready to accept tasks.
* This method becomes the critical one since 1.536, which introduces the
- * on-demand creation of executor threads. The entire logic should use
- * this method instead of {@link #isAlive()}, because it provides wrong
- * information for non-started threads.
+ * on-demand creation of executor threads. Callers should use
+ * this method instead of {@link #isAlive()}, which would be incorrect for
+ * non-started threads or running {@link AsynchronousExecution}.
* @return True if the executor is available for tasks
* @since 1.536
*/
public boolean isActive() {
- return !started || isAlive();
+ lock.readLock().lock();
+ try {
+ return !started || asynchronousExecution != null || isAlive();
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ /**
+ * If currently running in asynchronous mode, returns that handle.
+ * @since 1.607
+ */
+ public @CheckForNull AsynchronousExecution getAsynchronousExecution() {
+ lock.readLock().lock();
+ try {
+ return asynchronousExecution;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ /**
+ * If this executor is running an {@link AsynchronousExecution} and that execution wants to hide the display
+ * cell for the executor (because there is another executor displaying the job progress and we don't want to
+ * confuse the user) then this method will return {@code false} to indicate to {@code executors.jelly} that
+ * the executor cell should be hidden.
+ *
+ * @return {@code true} iff the {@code executorCell.jelly} for this {@link Executor} should be displayed in
+ * {@code executors.jelly}.
+ * @since 1.607
+ * @see AsynchronousExecution#displayCell()
+ */
+ public boolean isDisplayCell() {
+ AsynchronousExecution asynchronousExecution = getAsynchronousExecution();
+ return asynchronousExecution == null || asynchronousExecution.displayCell();
}
/**
* Returns true if this executor is waiting for a task to execute.
*/
public boolean isParking() {
- return !started;
+ lock.readLock().lock();
+ try {
+ return !started;
+ } finally {
+ lock.readLock().unlock();
+ }
}
/**
* If this thread dies unexpectedly, obtain the cause of the failure.
*
- * @return null if the death is expected death or the thread is {@link #isAlive() still alive}.
+ * @return null if the death is expected death or the thread {@link #isActive}.
* @since 1.142
*/
public Throwable getCauseOfDeath() {
@@ -392,13 +632,24 @@ public class Executor extends Thread implements ModelObject {
*/
@Exported
public int getProgress() {
- Queue.Executable e = executable;
- if(e==null) return -1;
- long d = Executables.getEstimatedDurationFor(e);
- if(d<0) return -1;
+ long d;
+ lock.readLock().lock();
+ try {
+ if (executable == null) {
+ return -1;
+ }
+ d = Executables.getEstimatedDurationFor(executable);
+ } finally {
+ lock.readLock().unlock();
+ }
+ if (d <= 0) {
+ return -1;
+ }
- int num = (int)(getElapsedTime()*100/d);
- if(num>=100) num=99;
+ int num = (int) (getElapsedTime() * 100 / d);
+ if (num >= 100) {
+ num = 99;
+ }
return num;
}
@@ -411,22 +662,35 @@ public class Executor extends Thread implements ModelObject {
*/
@Exported
public boolean isLikelyStuck() {
- Queue.Executable e = executable;
- if(e==null) return false;
+ long d;
+ long elapsed;
+ lock.readLock().lock();
+ try {
+ if (executable == null) {
+ return false;
+ }
- long elapsed = getElapsedTime();
- long d = Executables.getEstimatedDurationFor(e);
- if(d>=0) {
+ elapsed = getElapsedTime();
+ d = Executables.getEstimatedDurationFor(executable);
+ } finally {
+ lock.readLock().unlock();
+ }
+ if (d >= 0) {
// if it's taking 10 times longer than ETA, consider it stuck
- return d*10 < elapsed;
+ return d * 10 < elapsed;
} else {
// if no ETA is available, a build taking longer than a day is considered stuck
- return TimeUnit2.MILLISECONDS.toHours(elapsed)>24;
+ return TimeUnit2.MILLISECONDS.toHours(elapsed) > 24;
}
}
public long getElapsedTime() {
- return System.currentTimeMillis() - startTime;
+ lock.readLock().lock();
+ try {
+ return System.currentTimeMillis() - startTime;
+ } finally {
+ lock.readLock().unlock();
+ }
}
/**
@@ -435,7 +699,12 @@ public class Executor extends Thread implements ModelObject {
* @since 1.440
*/
public long getTimeSpentInQueue() {
- return startTime - workUnit.context.item.buildableStartMilliseconds;
+ lock.readLock().lock();
+ try {
+ return startTime - workUnit.context.item.buildableStartMilliseconds;
+ } finally {
+ lock.readLock().unlock();
+ }
}
/**
@@ -453,14 +722,25 @@ public class Executor extends Thread implements ModelObject {
* until the build completes.
*/
public String getEstimatedRemainingTime() {
- Queue.Executable e = executable;
- if(e==null) return Messages.Executor_NotAvailable();
+ long d;
+ lock.readLock().lock();
+ try {
+ if (executable == null) {
+ return Messages.Executor_NotAvailable();
+ }
- long d = Executables.getEstimatedDurationFor(e);
- if(d<0) return Messages.Executor_NotAvailable();
+ d = Executables.getEstimatedDurationFor(executable);
+ } finally {
+ lock.readLock().unlock();
+ }
+ if (d < 0) {
+ return Messages.Executor_NotAvailable();
+ }
- long eta = d-getElapsedTime();
- if(eta<=0) return Messages.Executor_NotAvailable();
+ long eta = d - getElapsedTime();
+ if (eta <= 0) {
+ return Messages.Executor_NotAvailable();
+ }
return Util.getTimeSpanString(eta);
}
@@ -470,14 +750,25 @@ public class Executor extends Thread implements ModelObject {
* it as a number of milli-seconds.
*/
public long getEstimatedRemainingTimeMillis() {
- Queue.Executable e = executable;
- if(e==null) return -1;
+ long d;
+ lock.readLock().lock();
+ try {
+ if (executable == null) {
+ return -1;
+ }
- long d = Executables.getEstimatedDurationFor(e);
- if(d<0) return -1;
+ d = Executables.getEstimatedDurationFor(executable);
+ } finally {
+ lock.readLock().unlock();
+ }
+ if (d < 0) {
+ return -1;
+ }
- long eta = d-getElapsedTime();
- if(eta<=0) return -1;
+ long eta = d - getElapsedTime();
+ if (eta <= 0) {
+ return -1;
+ }
return eta;
}
@@ -488,14 +779,19 @@ public class Executor extends Thread implements ModelObject {
* @see #start(WorkUnit)
*/
@Override
- public synchronized void start() {
+ public void start() {
throw new UnsupportedOperationException();
}
- /*protected*/ synchronized void start(WorkUnit task) {
- this.workUnit = task;
- super.start();
- started = true;
+ /*protected*/ void start(WorkUnit task) {
+ lock.writeLock().lock();
+ try {
+ this.workUnit = task;
+ super.start();
+ started = true;
+ } finally {
+ lock.writeLock().unlock();
+ }
}
@@ -504,6 +800,7 @@ public class Executor extends Thread implements ModelObject {
* Use {@link #doStop()}.
*/
@RequirePOST
+ @Deprecated
public void doStop( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
doStop().generateResponse(req,rsp,this);
}
@@ -515,10 +812,14 @@ public class Executor extends Thread implements ModelObject {
*/
@RequirePOST
public HttpResponse doStop() {
- Queue.Executable e = executable;
- if(e!=null) {
- Tasks.getOwnerTaskOf(getParentOf(e)).checkAbortPermission();
- interrupt();
+ lock.writeLock().lock(); // need write lock as interrupt will change the field
+ try {
+ if (executable != null) {
+ Tasks.getOwnerTaskOf(getParentOf(executable)).checkAbortPermission();
+ interrupt();
+ }
+ } finally {
+ lock.writeLock().unlock();
}
return HttpResponses.forwardToPreviousPage();
}
@@ -529,7 +830,7 @@ public class Executor extends Thread implements ModelObject {
@RequirePOST
public HttpResponse doYank() {
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
- if (isAlive())
+ if (isActive())
throw new Failure("Can't yank a live executor");
owner.removeExecutor(this);
return HttpResponses.redirectViaContextPath("/");
@@ -539,8 +840,12 @@ public class Executor extends Thread implements ModelObject {
* Checks if the current user has a permission to stop this build.
*/
public boolean hasStopPermission() {
- Queue.Executable e = executable;
- return e!=null && Tasks.getOwnerTaskOf(getParentOf(e)).hasAbortPermission();
+ lock.readLock().lock();
+ try {
+ return executable != null && Tasks.getOwnerTaskOf(getParentOf(executable)).hasAbortPermission();
+ } finally {
+ lock.readLock().unlock();
+ }
}
public @Nonnull Computer getOwner() {
@@ -551,11 +856,16 @@ public class Executor extends Thread implements ModelObject {
* Returns when this executor started or should start being idle.
*/
public long getIdleStartMilliseconds() {
- if (isIdle())
- return Math.max(creationTime, owner.getConnectTime());
- else {
- return Math.max(startTime + Math.max(0, Executables.getEstimatedDurationFor(executable)),
- System.currentTimeMillis() + 15000);
+ lock.readLock().lock();
+ try {
+ if (isIdle())
+ return Math.max(creationTime, owner.getConnectTime());
+ else {
+ return Math.max(startTime + Math.max(0, Executables.getEstimatedDurationFor(executable)),
+ System.currentTimeMillis() + 15000);
+ }
+ } finally {
+ lock.readLock().unlock();
}
}
@@ -593,6 +903,33 @@ public class Executor extends Thread implements ModelObject {
return IMPERSONATION.get();
}
+ /**
+ * Finds the executor currently running a given process.
+ * @param executable a possibly running executable
+ * @return the executor (possibly a {@link OneOffExecutor}) whose {@link Executor#getCurrentExecutable} matches that,
+ * or null if it could not be found (for example because the execution has already completed)
+ * @since 1.607
+ */
+ public static @CheckForNull Executor of(Executable executable) {
+ Jenkins jenkins = Jenkins.getInstance();
+ if (jenkins == null) {
+ return null;
+ }
+ for (Computer computer : jenkins.getComputers()) {
+ for (Executor executor : computer.getExecutors()) {
+ if (executor.getCurrentExecutable() == executable) {
+ return executor;
+ }
+ }
+ for (Executor executor : computer.getOneOffExecutors()) {
+ if (executor.getCurrentExecutable() == executable) {
+ return executor;
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Returns the estimated duration for the executable.
* Protects against {@link AbstractMethodError}s if the {@link Executable} implementation
@@ -601,6 +938,7 @@ public class Executor extends Thread implements ModelObject {
* @deprecated as of 1.388
* Use {@link Executables#getEstimatedDurationFor(Queue.Executable)}
*/
+ @Deprecated
public static long getEstimatedDurationFor(Executable e) {
return Executables.getEstimatedDurationFor(e);
}
diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java
index 22fac5bb8d..38927e5a11 100644
--- a/core/src/main/java/hudson/model/Fingerprint.java
+++ b/core/src/main/java/hudson/model/Fingerprint.java
@@ -71,6 +71,7 @@ import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
import org.xmlpull.v1.XmlPullParserException;
/**
@@ -761,13 +762,16 @@ public class Fingerprint implements ModelObject, Saveable {
private static final DateConverter DATE_CONVERTER = new DateConverter();
- private final Date timestamp;
+ /**
+ * Time when the fingerprint has been captured.
+ */
+ private final @Nonnull Date timestamp;
/**
* Null if this fingerprint is for a file that's
* apparently produced outside.
*/
- private final BuildPtr original;
+ private final @CheckForNull BuildPtr original;
private final byte[] md5sum;
@@ -785,12 +789,12 @@ public class Fingerprint implements ModelObject, Saveable {
*/
private transient volatile List transientFacets = null;
- public Fingerprint(Run build, String fileName, byte[] md5sum) throws IOException {
+ public Fingerprint(@CheckForNull Run build, @Nonnull String fileName, @Nonnull byte[] md5sum) throws IOException {
this(build==null ? null : new BuildPtr(build), fileName, md5sum);
save();
}
- Fingerprint(BuildPtr original, String fileName, byte[] md5sum) {
+ Fingerprint(@CheckForNull BuildPtr original, @Nonnull String fileName, @Nonnull byte[] md5sum) {
this.original = original;
this.md5sum = md5sum;
this.fileName = fileName;
@@ -809,11 +813,11 @@ public class Fingerprint implements ModelObject, Saveable {
* if the file is apparently created outside Hudson.
*/
@Exported
- public BuildPtr getOriginal() {
+ public @CheckForNull BuildPtr getOriginal() {
return original;
}
- public String getDisplayName() {
+ public @Nonnull String getDisplayName() {
return fileName;
}
@@ -821,7 +825,7 @@ public class Fingerprint implements ModelObject, Saveable {
* The file name (like "foo.jar" without path).
*/
@Exported
- public String getFileName() {
+ public @Nonnull String getFileName() {
return fileName;
}
@@ -829,7 +833,7 @@ public class Fingerprint implements ModelObject, Saveable {
* Gets the MD5 hash string.
*/
@Exported(name="hash")
- public String getHashString() {
+ public @Nonnull String getHashString() {
return Util.toHexString(md5sum);
}
@@ -837,7 +841,7 @@ public class Fingerprint implements ModelObject, Saveable {
* Gets the timestamp when this record is created.
*/
@Exported
- public Date getTimestamp() {
+ public @Nonnull Date getTimestamp() {
return timestamp;
}
@@ -847,7 +851,7 @@ public class Fingerprint implements ModelObject, Saveable {
* @return
* string like "3 minutes" "1 day" etc.
*/
- public String getTimestampString() {
+ public @Nonnull String getTimestampString() {
long duration = System.currentTimeMillis()-timestamp.getTime();
return Util.getPastTimeString(duration);
}
@@ -857,8 +861,9 @@ public class Fingerprint implements ModelObject, Saveable {
*
*
* These builds of this job has used this file.
+ * @return may be empty but not null.
*/
- public RangeSet getRangeSet(String jobFullName) {
+ public @Nonnull RangeSet getRangeSet(String jobFullName) {
RangeSet r = usages.get(jobFullName);
if(r==null) r = new RangeSet();
return r;
@@ -871,14 +876,14 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* Gets the sorted list of job names where this jar is used.
*/
- public List getJobs() {
+ public @Nonnull List getJobs() {
List r = new ArrayList();
r.addAll(usages.keySet());
Collections.sort(r);
return r;
}
- public Hashtable getUsages() {
+ public @Nonnull Hashtable getUsages() {
return usages;
}
@@ -897,34 +902,39 @@ public class Fingerprint implements ModelObject, Saveable {
// this is for remote API
@Exported(name="usage")
- public List _getUsages() {
+ public @Nonnull List _getUsages() {
List r = new ArrayList();
for (Entry e : usages.entrySet())
r.add(new RangeItem(e.getKey(),e.getValue()));
return r;
}
+ /**
+ * @deprecated Use {@link #addFor(hudson.model.Run)}
+ */
@Deprecated
- public synchronized void add(AbstractBuild b) throws IOException {
+ public synchronized void add(@Nonnull AbstractBuild b) throws IOException {
addFor((Run) b);
}
/**
+ * Adds a usage reference to the build.
+ * @param b {@link Run} to be referenced in {@link #usages}
* @since 1.577
*/
- public synchronized void addFor(Run b) throws IOException {
+ public synchronized void addFor(@Nonnull Run b) throws IOException {
add(b.getParent().getFullName(), b.getNumber());
}
/**
* Records that a build of a job has used this file.
*/
- public synchronized void add(String jobFullName, int n) throws IOException {
+ public synchronized void add(@Nonnull String jobFullName, int n) throws IOException {
addWithoutSaving(jobFullName, n);
save();
}
- void addWithoutSaving(String jobFullName, int n) {
+ void addWithoutSaving(@Nonnull String jobFullName, int n) {
synchronized(usages) { // TODO why not synchronized (this) like some, though not all, other accesses?
RangeSet r = usages.get(jobFullName);
if(r==null) {
@@ -968,6 +978,8 @@ public class Fingerprint implements ModelObject, Saveable {
*
* @return true
* if this record was modified.
+ *
+ * @throws IOException Save failure
*/
public synchronized boolean trim() throws IOException {
boolean modified = false;
@@ -1041,7 +1053,7 @@ public class Fingerprint implements ModelObject, Saveable {
*
* @since 1.421
*/
- public Collection getFacets() {
+ public @Nonnull Collection getFacets() {
if (transientFacets==null) {
List transientFacets = new ArrayList();
for (TransientFingerprintFacetFactory fff : TransientFingerprintFacetFactory.all()) {
@@ -1079,7 +1091,11 @@ public class Fingerprint implements ModelObject, Saveable {
};
}
- public Collection getSortedFacets() {
+ /**
+ * Sorts {@link FingerprintFacet}s by their timestamps.
+ * @return Sorted list of {@link FingerprintFacet}s
+ */
+ public @Nonnull Collection getSortedFacets() {
List r = new ArrayList(getFacets());
Collections.sort(r,new Comparator() {
public int compare(FingerprintFacet o1, FingerprintFacet o2) {
@@ -1095,8 +1111,11 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* Finds a facet of the specific type (including subtypes.)
+ * @param Class of the {@link FingerprintFacet}
+ * @return First matching facet of the specified class
+ * @since 1.556
*/
- public T getFacet(Class type) {
+ public @CheckForNull T getFacet(Class type) {
for (FingerprintFacet f : getFacets()) {
if (type.isInstance(f))
return type.cast(f);
@@ -1107,7 +1126,7 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* Returns the actions contributed from {@link #getFacets()}
*/
- public List getActions() {
+ public @Nonnull List getActions() {
List r = new ArrayList();
for (FingerprintFacet ff : getFacets())
ff.createActions(r);
@@ -1116,6 +1135,7 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* Save the settings to a file.
+ * @throws IOException Save error
*/
public synchronized void save() throws IOException {
if(BulkChange.contains(this)) return;
@@ -1218,14 +1238,14 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* The file we save our configuration.
*/
- private static XmlFile getConfigFile(File file) {
+ private static @Nonnull XmlFile getConfigFile(@Nonnull File file) {
return new XmlFile(XSTREAM,file);
}
/**
* Determines the file name from md5sum.
*/
- private static File getFingerprintFile(byte[] md5sum) {
+ private static @Nonnull File getFingerprintFile(@Nonnull byte[] md5sum) {
assert md5sum.length==16;
return new File( Jenkins.getInstance().getRootDir(),
"fingerprints/"+ Util.toHexString(md5sum,0,1)+'/'+Util.toHexString(md5sum,1,1)+'/'+Util.toHexString(md5sum,2,md5sum.length-2)+".xml");
@@ -1233,11 +1253,13 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* Loads a {@link Fingerprint} from a file in the image.
+ * @return Loaded {@link Fingerprint}. Null if the config file does not exist or
+ * malformed.
*/
- /*package*/ static @CheckForNull Fingerprint load(byte[] md5sum) throws IOException {
+ /*package*/ static @CheckForNull Fingerprint load(@Nonnull byte[] md5sum) throws IOException {
return load(getFingerprintFile(md5sum));
}
- /*package*/ static @CheckForNull Fingerprint load(File file) throws IOException {
+ /*package*/ static @CheckForNull Fingerprint load(@Nonnull File file) throws IOException {
XmlFile configFile = getConfigFile(file);
if(!configFile.exists())
return null;
diff --git a/core/src/main/java/hudson/model/FingerprintMap.java b/core/src/main/java/hudson/model/FingerprintMap.java
index f7112efa3d..7760bd0ada 100644
--- a/core/src/main/java/hudson/model/FingerprintMap.java
+++ b/core/src/main/java/hudson/model/FingerprintMap.java
@@ -30,6 +30,8 @@ import jenkins.model.Jenkins;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
/**
* Cache of {@link Fingerprint}s.
@@ -56,16 +58,17 @@ public final class FingerprintMap extends KeyedDataStorage i
/**
* @deprecated as of 1.390
*/
+ @Deprecated
public FreeStyleProject(Jenkins parent, String name) {
super(parent, name);
}
diff --git a/core/src/main/java/hudson/model/Hudson.java b/core/src/main/java/hudson/model/Hudson.java
index c4b6cf4a61..17b5721a45 100644
--- a/core/src/main/java/hudson/model/Hudson.java
+++ b/core/src/main/java/hudson/model/Hudson.java
@@ -60,12 +60,14 @@ public class Hudson extends Jenkins {
* List of registered {@link hudson.model.listeners.ItemListener}s.
* @deprecated as of 1.286
*/
+ @Deprecated
private transient final CopyOnWriteList itemListeners = ExtensionListView.createCopyOnWriteList(ItemListener.class);
/**
* List of registered {@link hudson.slaves.ComputerListener}s.
* @deprecated as of 1.286
*/
+ @Deprecated
private transient final CopyOnWriteList computerListeners = ExtensionListView.createCopyOnWriteList(ComputerListener.class);
/** @deprecated Here only for compatibility. Use {@link Jenkins#getInstance} instead. */
@@ -89,6 +91,7 @@ public class Hudson extends Jenkins {
* @deprecated as of 1.286.
* Use {@link ItemListener#all()}.
*/
+ @Deprecated
public CopyOnWriteList getJobListeners() {
return itemListeners;
}
@@ -99,6 +102,7 @@ public class Hudson extends Jenkins {
* @deprecated as of 1.286.
* Use {@link ComputerListener#all()}.
*/
+ @Deprecated
public CopyOnWriteList getComputerListeners() {
return computerListeners;
}
@@ -109,6 +113,7 @@ public class Hudson extends Jenkins {
* @deprecated
* Use {@link #getNode(String)}. Since 1.252.
*/
+ @Deprecated
public Slave getSlave(String name) {
Node n = getNode(name);
if (n instanceof Slave)
@@ -120,8 +125,9 @@ public class Hudson extends Jenkins {
* @deprecated
* Use {@link #getNodes()}. Since 1.252.
*/
+ @Deprecated
public List getSlaves() {
- return (List)slaves;
+ return (List)getNodes();
}
/**
@@ -130,6 +136,7 @@ public class Hudson extends Jenkins {
* @deprecated
* Use {@link #setNodes(List)}. Since 1.252.
*/
+ @Deprecated
public void setSlaves(List slaves) throws IOException {
setNodes(slaves);
}
@@ -139,6 +146,7 @@ public class Hudson extends Jenkins {
* Left only for the compatibility of URLs.
* Should not be invoked for any other purpose.
*/
+ @Deprecated
public TopLevelItem getJob(String name) {
return getItem(name);
}
@@ -147,6 +155,7 @@ public class Hudson extends Jenkins {
* @deprecated
* Used only for mapping jobs to URL in a case-insensitive fashion.
*/
+ @Deprecated
public TopLevelItem getJobCaseInsensitive(String name) {
String match = Functions.toEmailSafeString(name);
for(TopLevelItem item : getItems()) {
@@ -161,6 +170,7 @@ public class Hudson extends Jenkins {
* @deprecated as of 1.317
* Use {@link #doQuietDown()} instead.
*/
+ @Deprecated
public synchronized void doQuietDown(StaplerResponse rsp) throws IOException, ServletException {
doQuietDown().generateResponse(null, rsp, this);
}
@@ -171,6 +181,7 @@ public class Hudson extends Jenkins {
* @deprecated
* As on 1.267, moved to "/log/rss..."
*/
+ @Deprecated
public void doLogRss( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
String qs = req.getQueryString();
rsp.sendRedirect2("./log/rss"+(qs==null?"":'?'+qs));
@@ -180,6 +191,7 @@ public class Hudson extends Jenkins {
* @deprecated as of 1.294
* Define your own check method, instead of relying on this generic one.
*/
+ @Deprecated
public void doFieldCheck(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
doFieldCheck(
fixEmpty(req.getParameter("value")),
@@ -201,6 +213,7 @@ public class Hudson extends Jenkins {
* Either use client-side validation (e.g. class="required number")
* or define your own check method, instead of relying on this generic one.
*/
+ @Deprecated
public FormValidation doFieldCheck(@QueryParameter(fixEmpty=true) String value,
@QueryParameter(fixEmpty=true) String type,
@QueryParameter(fixEmpty=true) String errorText,
@@ -236,6 +249,7 @@ public class Hudson extends Jenkins {
* @deprecated
* Use {@link Functions#isWindows()}.
*/
+ @Deprecated
public static boolean isWindows() {
return File.pathSeparatorChar==';';
}
@@ -244,6 +258,7 @@ public class Hudson extends Jenkins {
* @deprecated
* Use {@link hudson.Platform#isDarwin()}
*/
+ @Deprecated
public static boolean isDarwin() {
return Platform.isDarwin();
}
@@ -252,6 +267,7 @@ public class Hudson extends Jenkins {
* @deprecated since 2007-12-18.
* Use {@link #checkPermission(hudson.security.Permission)}
*/
+ @Deprecated
public static boolean adminCheck() throws IOException {
return adminCheck(Stapler.getCurrentRequest(), Stapler.getCurrentResponse());
}
@@ -260,6 +276,7 @@ public class Hudson extends Jenkins {
* @deprecated since 2007-12-18.
* Use {@link #checkPermission(hudson.security.Permission)}
*/
+ @Deprecated
public static boolean adminCheck(StaplerRequest req,StaplerResponse rsp) throws IOException {
if (isAdmin(req)) return true;
@@ -285,6 +302,7 @@ public class Hudson extends Jenkins {
* if appropriate), then identify a suitable {@link hudson.security.AccessControlled} object to check its permission
* against.
*/
+ @Deprecated
public static boolean isAdmin() {
return Jenkins.getInstance().getACL().hasPermission(ADMINISTER);
}
@@ -294,6 +312,7 @@ public class Hudson extends Jenkins {
* Define a custom {@link hudson.security.Permission} and check against ACL.
* See {@link #isAdmin()} for more instructions.
*/
+ @Deprecated
public static boolean isAdmin(StaplerRequest req) {
return isAdmin();
}
@@ -305,6 +324,7 @@ public class Hudson extends Jenkins {
/**
* @deprecated only here for backward comp
*/
+ @Deprecated
public static final class MasterComputer extends Jenkins.MasterComputer {
// no op
}
@@ -312,6 +332,7 @@ public class Hudson extends Jenkins {
/**
* @deprecated only here for backward comp
*/
+ @Deprecated
public static class CloudList extends Jenkins.CloudList {
public CloudList(Jenkins h) {
super(h);
diff --git a/core/src/main/java/hudson/model/Item.java b/core/src/main/java/hudson/model/Item.java
index 726796fa8e..94ee4181de 100644
--- a/core/src/main/java/hudson/model/Item.java
+++ b/core/src/main/java/hudson/model/Item.java
@@ -176,6 +176,7 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont
* This method is only intended for the remote API clients who cannot resolve relative references
* (even this won't work for the same reason, which should be fixed.)
*/
+ @Deprecated
String getAbsoluteUrl();
/**
diff --git a/core/src/main/java/hudson/model/ItemGroupMixIn.java b/core/src/main/java/hudson/model/ItemGroupMixIn.java
index d34b5e7f93..ecf4d2d6e8 100644
--- a/core/src/main/java/hudson/model/ItemGroupMixIn.java
+++ b/core/src/main/java/hudson/model/ItemGroupMixIn.java
@@ -31,6 +31,7 @@ import hudson.util.CopyOnWriteMap;
import hudson.util.Function1;
import hudson.util.IOUtils;
import jenkins.model.Jenkins;
+import org.acegisecurity.AccessDeniedException;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
@@ -114,7 +115,7 @@ public abstract class ItemGroupMixIn {
item.onLoad(parent, subdir.getName());
}
configurations.put(key.call(item), item);
- } catch (IOException e) {
+ } catch (Exception e) {
Logger.getLogger(ItemGroupMixIn.class.getName()).log(Level.WARNING, "could not load " + subdir, e);
}
}
@@ -190,6 +191,8 @@ public abstract class ItemGroupMixIn {
if (descriptor == null) {
throw new Failure("No item type ‘" + mode + "’ is known");
}
+ descriptor.checkApplicableIn(parent);
+ acl.getACL().checkCreatePermission(parent, descriptor);
// create empty job and redirect to the project config screen
result = createProject(descriptor, name, true);
@@ -214,6 +217,8 @@ public abstract class ItemGroupMixIn {
public synchronized T copy(T src, String name) throws IOException {
acl.checkPermission(Item.CREATE);
src.checkPermission(Item.EXTENDED_READ);
+ src.getDescriptor().checkApplicableIn(parent);
+ acl.getACL().checkCreatePermission(parent, src.getDescriptor());
T result = (T)createProject(src.getDescriptor(),name,false);
@@ -249,6 +254,7 @@ public abstract class ItemGroupMixIn {
File configXml = Items.getConfigFile(getRootDirFor(name)).getFile();
final File dir = configXml.getParentFile();
dir.mkdirs();
+ boolean success = false;
try {
IOUtils.copy(xml,configXml);
@@ -258,6 +264,10 @@ public abstract class ItemGroupMixIn {
return (TopLevelItem) Items.load(parent, dir);
}
});
+
+ success = acl.getACL().hasCreatePermission(Jenkins.getAuthentication(), parent, result.getDescriptor())
+ && result.getDescriptor().isApplicableIn(parent);
+
add(result);
ItemListener.fireOnCreated(result);
@@ -265,15 +275,24 @@ public abstract class ItemGroupMixIn {
return result;
} catch (IOException e) {
- // if anything fails, delete the config file to avoid further confusion
- Util.deleteRecursive(dir);
+ success = false;
throw e;
+ } catch (RuntimeException e) {
+ success = false;
+ throw e;
+ } finally {
+ if (!success) {
+ // if anything fails, delete the config file to avoid further confusion
+ Util.deleteRecursive(dir);
+ }
}
}
public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify )
throws IOException {
acl.checkPermission(Item.CREATE);
+ type.checkApplicableIn(parent);
+ acl.getACL().checkCreatePermission(parent, type);
Jenkins.getInstance().getProjectNamingStrategy().checkName(name);
if(parent.getItem(name)!=null)
diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java
index 0358e4471a..26efd209b5 100644
--- a/core/src/main/java/hudson/model/Items.java
+++ b/core/src/main/java/hudson/model/Items.java
@@ -29,11 +29,14 @@ import hudson.Extension;
import hudson.XmlFile;
import hudson.model.listeners.ItemListener;
import hudson.remoting.Callable;
+import hudson.security.ACL;
+import hudson.security.AccessControlled;
import hudson.triggers.Trigger;
import hudson.util.DescriptorList;
import hudson.util.EditDistance;
import hudson.util.XStream2;
import jenkins.model.Jenkins;
+import org.acegisecurity.Authentication;
import org.apache.commons.lang.StringUtils;
import java.io.File;
@@ -46,6 +49,8 @@ import java.util.List;
import java.util.Stack;
import java.util.StringTokenizer;
import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
import jenkins.model.DirectlyModifiableTopLevelItemGroup;
import org.apache.commons.io.FileUtils;
@@ -61,6 +66,7 @@ public class Items {
* @deprecated as of 1.286
* Use {@link #all()} for read access and {@link Extension} for registration.
*/
+ @Deprecated
public static final List LIST = (List)new DescriptorList(TopLevelItem.class);
/**
@@ -110,6 +116,42 @@ public class Items {
return Jenkins.getInstance().getDescriptorList(TopLevelItem.class);
}
+ /**
+ * Returns all the registered {@link TopLevelItemDescriptor}s that the current security principal is allowed to
+ * create within the specified item group.
+ *
+ * @since TODO
+ */
+ public static List all(ItemGroup c) {
+ return all(Jenkins.getAuthentication(), c);
+ }
+
+ /**
+ * Returns all the registered {@link TopLevelItemDescriptor}s that the specified security principal is allowed to
+ * create within the specified item group.
+ *
+ * @since TODO
+ */
+ public static List all(Authentication a, ItemGroup c) {
+ List result = new ArrayList();
+ ACL acl;
+ if (c instanceof AccessControlled) {
+ acl = ((AccessControlled) c).getACL();
+ } else {
+ // fall back to root
+ acl = Jenkins.getInstance().getACL();
+ }
+ for (TopLevelItemDescriptor d: all()) {
+ if (acl.hasCreatePermission(a, c, d) && d.isApplicableIn(c)) {
+ result.add(d);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @deprecated Underspecified what the parameter is. {@link Descriptor#getId}? A {@link Describable} class name?
+ */
public static TopLevelItemDescriptor getDescriptor(String fqcn) {
return Descriptor.find(all(), fqcn);
}
@@ -131,6 +173,7 @@ public class Items {
* @deprecated as of 1.406
* Use {@link #fromNameList(ItemGroup, String, Class)}
*/
+ @Deprecated
public static List fromNameList(String list, Class type) {
return fromNameList(null,list,type);
}
@@ -138,7 +181,7 @@ public class Items {
/**
* Does the opposite of {@link #toNameList(Collection)}.
*/
- public static List fromNameList(ItemGroup context, String list, Class type) {
+ public static List fromNameList(ItemGroup context, @Nonnull String list, @Nonnull Class type) {
Jenkins hudson = Jenkins.getInstance();
List r = new ArrayList();
diff --git a/core/src/main/java/hudson/model/JDK.java b/core/src/main/java/hudson/model/JDK.java
index a06bac9c64..2ba4490288 100644
--- a/core/src/main/java/hudson/model/JDK.java
+++ b/core/src/main/java/hudson/model/JDK.java
@@ -80,6 +80,7 @@ public final class JDK extends ToolInstallation implements NodeSpecific, En
* @deprecated as of 1.304
* Use {@link #getHome()}
*/
+ @Deprecated
public String getJavaHome() {
return getHome();
}
@@ -108,6 +109,7 @@ public final class JDK extends ToolInstallation implements NodeSpecific, En
/**
* @deprecated as of 1.460. Use {@link #buildEnvVars(EnvVars)}
*/
+ @Deprecated
public void buildEnvVars(Map env) {
String home = getHome();
if (home == null) {
@@ -182,7 +184,11 @@ public final class JDK extends ToolInstallation implements NodeSpecific, En
@Override protected FormValidation checkHomeDirectory(File value) {
File toolsJar = new File(value,"lib/tools.jar");
File mac = new File(value,"lib/dt.jar");
- if(!toolsJar.exists() && !mac.exists())
+
+ // JENKINS-25601: JDK 9+ no longer has tools.jar. Keep the existing dt.jar/tools.jar checks to be safe.
+ File javac = new File(value, "bin/javac");
+ File javacExe = new File(value, "bin/javac.exe");
+ if(!toolsJar.exists() && !mac.exists() && !javac.exists() && !javacExe.exists())
return FormValidation.error(Messages.Hudson_NotJDKDir(value));
return FormValidation.ok();
diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java
index 7020de7ee3..286c7aba32 100644
--- a/core/src/main/java/hudson/model/Job.java
+++ b/core/src/main/java/hudson/model/Job.java
@@ -105,7 +105,10 @@ import javax.annotation.Nonnull;
import static javax.servlet.http.HttpServletResponse.*;
import jenkins.model.ModelObjectWithChildren;
+import jenkins.model.RunIdMigrator;
import jenkins.model.lazy.LazyBuildMixIn;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* A job is an runnable entity under the monitoring of Hudson.
@@ -162,6 +165,9 @@ public abstract class Job, RunT extends Run> properties = new CopyOnWriteList>();
+ @Restricted(NoExternalUse.class)
+ public transient RunIdMigrator runIdMigrator;
+
protected Job(ItemGroup parent, String name) {
super(parent, name);
}
@@ -172,11 +178,21 @@ public abstract class Job, RunT extends Run parent, String name)
throws IOException {
super.onLoad(parent, name);
+ File buildDir = getBuildDir();
+ runIdMigrator = new RunIdMigrator();
+ runIdMigrator.migrate(buildDir, Jenkins.getInstance().getRootDir());
+
TextFile f = getNextBuildNumberFile();
if (f.exists()) {
// starting 1.28, we store nextBuildNumber in a separate file.
@@ -188,7 +204,7 @@ public abstract class Job, RunT extends Run, RunT extends Run, RunT extends Run, RunT extends Run, RunT extends Run getBuildsByTimestamp(long start, long end) {
return getBuilds().byTimestamp(start,end);
}
diff --git a/core/src/main/java/hudson/model/JobProperty.java b/core/src/main/java/hudson/model/JobProperty.java
index affc3bfd71..600c5e1fb1 100644
--- a/core/src/main/java/hudson/model/JobProperty.java
+++ b/core/src/main/java/hudson/model/JobProperty.java
@@ -104,6 +104,7 @@ public abstract class JobProperty> implements ReconfigurableD
* @deprecated
* as of 1.341. Override {@link #getJobActions(Job)} instead.
*/
+ @Deprecated
public Action getJobAction(J job) {
return null;
}
diff --git a/core/src/main/java/hudson/model/Jobs.java b/core/src/main/java/hudson/model/Jobs.java
index c124861603..e6a13e68d7 100644
--- a/core/src/main/java/hudson/model/Jobs.java
+++ b/core/src/main/java/hudson/model/Jobs.java
@@ -34,6 +34,7 @@ import java.util.List;
* @author Kohsuke Kawaguchi
* @deprecated since 1.281
*/
+@Deprecated
public class Jobs {
/**
* List of all installed {@link JobPropertyDescriptor} types.
@@ -47,6 +48,7 @@ public class Jobs {
* Use {@link JobPropertyDescriptor#all()} for read access,
* and {@link Extension} for registration.
*/
+ @Deprecated
public static final List PROPERTIES = (List)
new DescriptorList>((Class)JobProperty.class);
}
diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java
index cdfcfb5e86..f0a2df2474 100644
--- a/core/src/main/java/hudson/model/Label.java
+++ b/core/src/main/java/hudson/model/Label.java
@@ -39,6 +39,7 @@ import hudson.model.labels.LabelExpressionLexer;
import hudson.model.labels.LabelExpressionParser;
import hudson.model.labels.LabelOperatorPrecedence;
import hudson.model.labels.LabelVisitor;
+import hudson.model.queue.SubTask;
import hudson.security.ACL;
import hudson.slaves.NodeProvisioner;
import hudson.slaves.Cloud;
@@ -107,6 +108,17 @@ public abstract class Label extends Actionable implements Comparable