cs = new ArrayList<>();
- if (depth > 0) {
- if (traversed.add(uc.upstreamUrl + uc.upstreamBuild)) {
- for (Cause c2 : uc.upstreamCauses) {
- cs.add(trim(c2, depth - 1, traversed));
+ if (traversed.add(uc.upstreamUrl + uc.upstreamBuild)) {
+ for (Cause c2 : uc.upstreamCauses) {
+ if (depth <= 0 || traversed.size() >= MAX_LEAF) {
+ cs.add(new DeeplyNestedUpstreamCause());
+ break;
}
+ cs.add(trim(c2, depth - 1, traversed));
}
- } else if (traversed.size() < MAX_LEAF) {
- cs.add(new DeeplyNestedUpstreamCause());
+ } else {
+ traversed.add(uc.upstreamUrl + uc.upstreamBuild + '#' + traversed.size());
}
return new UpstreamCause(uc.upstreamProject, uc.upstreamBuild, uc.upstreamUrl, cs);
}
diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java
index 0b40cc60eb18..020399bdd5b8 100644
--- a/core/src/main/java/hudson/model/Executor.java
+++ b/core/src/main/java/hudson/model/Executor.java
@@ -324,17 +324,19 @@ private void resetWorkUnit(String reason) {
@Override
public void run() {
- 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;
+ if (!(owner instanceof Jenkins.MasterComputer)) {
+ 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();
@@ -352,13 +354,15 @@ public void run() {
task = Queue.withLock(new 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;
+ if (!(owner instanceof Jenkins.MasterComputer)) {
+ 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.
diff --git a/core/src/main/java/hudson/model/FileParameterDefinition.java b/core/src/main/java/hudson/model/FileParameterDefinition.java
index 343dd5831203..25cb08336da3 100644
--- a/core/src/main/java/hudson/model/FileParameterDefinition.java
+++ b/core/src/main/java/hudson/model/FileParameterDefinition.java
@@ -35,7 +35,7 @@
import java.util.Objects;
import javax.servlet.ServletException;
import net.sf.json.JSONObject;
-import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload2.core.FileItem;
import org.apache.commons.io.FileUtils;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
@@ -90,7 +90,7 @@ public String getHelpFile() {
public ParameterValue createValue(StaplerRequest req) {
FileItem src;
try {
- src = req.getFileItem(getName());
+ src = req.getFileItem2(getName());
} catch (ServletException | IOException e) {
// Not sure what to do here. We might want to raise this
// but that would involve changing the throws for this call
diff --git a/core/src/main/java/hudson/model/FileParameterValue.java b/core/src/main/java/hudson/model/FileParameterValue.java
index e9e30e63463b..343e30bb64f4 100644
--- a/core/src/main/java/hudson/model/FileParameterValue.java
+++ b/core/src/main/java/hudson/model/FileParameterValue.java
@@ -39,12 +39,13 @@
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.regex.Pattern;
import jenkins.util.SystemProperties;
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.fileupload.FileItemHeaders;
-import org.apache.commons.fileupload.disk.DiskFileItem;
-import org.apache.commons.fileupload.util.FileItemHeadersImpl;
+import org.apache.commons.fileupload2.core.FileItem;
+import org.apache.commons.fileupload2.core.FileItemFactory;
+import org.apache.commons.fileupload2.core.FileItemHeaders;
+import org.apache.commons.fileupload2.core.FileItemHeadersProvider;
import org.apache.commons.io.FilenameUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -55,12 +56,6 @@
/**
* {@link ParameterValue} for {@link FileParameterDefinition}.
*
- * Persistence
- *
- * {@link DiskFileItem} is persistable via serialization,
- * (although the data may get very large in XML) so this object
- * as a whole is persistable.
- *
* @author Kohsuke Kawaguchi
*/
public class FileParameterValue extends ParameterValue {
@@ -97,8 +92,16 @@ public FileParameterValue(String name, FileItem file) {
this(name, file, FilenameUtils.getName(file.getName()));
}
+ /**
+ * @deprecated use {@link #FileParameterValue(String, FileItem)}
+ */
+ @Deprecated
+ public FileParameterValue(String name, org.apache.commons.fileupload.FileItem file) {
+ this(name, file.toFileUpload2FileItem(), FilenameUtils.getName(file.getName()));
+ }
+
public FileParameterValue(String name, File file, String originalFileName) {
- this(name, new FileItemImpl(file), originalFileName);
+ this(name, new FileItemImpl2(file), originalFileName);
}
protected FileParameterValue(String name, FileItem file, String originalFileName) {
@@ -146,10 +149,18 @@ public String getOriginalFileName() {
return originalFileName;
}
- public FileItem getFile() {
+ public FileItem getFile2() {
return file;
}
+ /**
+ * @deprecated use {@link #getFile2}
+ */
+ @Deprecated
+ public org.apache.commons.fileupload.FileItem getFile() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(getFile2());
+ }
+
@Override
public BuildWrapper createBuildWrapper(AbstractBuild, ?> build) {
return new BuildWrapper() {
@@ -248,11 +259,120 @@ private File getFileParameterFolderUnderBuild(AbstractBuild, ?> build) {
/**
* Default implementation from {@link File}.
+ *
+ * @deprecated use {@link FileItemImpl2}
*/
- public static final class FileItemImpl implements FileItem {
- private final File file;
+ @Deprecated
+ public static final class FileItemImpl implements org.apache.commons.fileupload.FileItem {
+ private final FileItem delegate;
public FileItemImpl(File file) {
+ if (file == null) {
+ throw new NullPointerException("file");
+ }
+ this.delegate = new FileItemImpl2(file);
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getInputStream();
+ }
+
+ @Override
+ public String getContentType() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getContentType();
+ }
+
+ @Override
+ @SuppressFBWarnings(value = "FILE_UPLOAD_FILENAME", justification = "for compatibility")
+ public String getName() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getName();
+ }
+
+ @Override
+ public boolean isInMemory() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).isInMemory();
+ }
+
+ @Override
+ public long getSize() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getSize();
+ }
+
+ @Override
+ public byte[] get() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).get();
+ }
+
+ @Override
+ public String getString(String encoding) throws UnsupportedEncodingException {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getString(encoding);
+ }
+
+ @Override
+ public String getString() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getString();
+ }
+
+ @Override
+ public void write(File to) throws Exception {
+ org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).write(to);
+ }
+
+ @Override
+ public void delete() {
+ org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).delete();
+ }
+
+ @Override
+ public String getFieldName() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getFieldName();
+ }
+
+ @Override
+ public void setFieldName(String name) {
+ org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).setFieldName(name);
+ }
+
+ @Override
+ public boolean isFormField() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).isFormField();
+ }
+
+ @Override
+ public void setFormField(boolean state) {
+ org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).setFormField(state);
+ }
+
+ @Override
+ @Deprecated
+ public OutputStream getOutputStream() throws IOException {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getOutputStream();
+ }
+
+ @Override
+ public org.apache.commons.fileupload.FileItemHeaders getHeaders() {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).getHeaders();
+ }
+
+ @Override
+ public void setHeaders(org.apache.commons.fileupload.FileItemHeaders headers) {
+ org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(delegate).setHeaders(headers);
+ }
+
+ @Override
+ public FileItem toFileUpload2FileItem() {
+ return delegate;
+ }
+ }
+
+ /**
+ * Default implementation from {@link File}.
+ */
+ public static final class FileItemImpl2 implements FileItem {
+ private final File file;
+
+ public FileItemImpl2(File file) {
if (file == null) {
throw new NullPointerException("file");
}
@@ -294,8 +414,12 @@ public byte[] get() {
}
@Override
- public String getString(String encoding) throws UnsupportedEncodingException {
- return new String(get(), encoding);
+ public String getString(Charset toCharset) throws IOException {
+ try {
+ return new String(get(), toCharset);
+ } catch (UncheckedIOException e) {
+ throw e.getCause();
+ }
}
@Override
@@ -304,17 +428,19 @@ public String getString() {
}
@Override
- public void write(File to) throws Exception {
- new FilePath(file).copyTo(new FilePath(to));
+ public FileItem write(Path to) throws IOException {
+ try {
+ new FilePath(file).copyTo(new FilePath(to.toFile()));
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ return this;
}
@Override
- public void delete() {
- try {
- Files.deleteIfExists(file.toPath());
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
+ public FileItem delete() throws IOException {
+ Files.deleteIfExists(Util.fileToPath(file));
+ return this;
}
@Override
@@ -323,7 +449,8 @@ public String getFieldName() {
}
@Override
- public void setFieldName(String name) {
+ public FileItem setFieldName(String name) {
+ return this;
}
@Override
@@ -332,7 +459,8 @@ public boolean isFormField() {
}
@Override
- public void setFormField(boolean state) {
+ public FileItem setFormField(boolean state) {
+ return this;
}
@Override
@@ -343,11 +471,12 @@ public OutputStream getOutputStream() throws IOException {
@Override
public FileItemHeaders getHeaders() {
- return new FileItemHeadersImpl();
+ return FileItemFactory.AbstractFileItemBuilder.newFileItemHeaders();
}
@Override
- public void setHeaders(FileItemHeaders headers) {
+ public FileItemHeadersProvider setHeaders(FileItemHeaders headers) {
+ return this;
}
}
}
diff --git a/core/src/main/java/hudson/model/FreeStyleProject.java b/core/src/main/java/hudson/model/FreeStyleProject.java
index c63a339d6611..5278d2a301bb 100644
--- a/core/src/main/java/hudson/model/FreeStyleProject.java
+++ b/core/src/main/java/hudson/model/FreeStyleProject.java
@@ -111,7 +111,7 @@ public String getIconFilePathPattern() {
@Override
public String getIconClassName() {
- return "icon-freestyle-project";
+ return "symbol-freestyle-project";
}
static {
diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java
index 8c4e3183100a..92f1d374fb08 100644
--- a/core/src/main/java/hudson/model/Job.java
+++ b/core/src/main/java/hudson/model/Job.java
@@ -626,11 +626,11 @@ public Collection> getOverrides() {
}
/**
- * @deprecated see {@link LazyBuildMixIn#createHistoryWidget()}
+ * @deprecated Remove any override, history widget is now created via {@link jenkins.widgets.WidgetFactory} implementation.
*/
@Deprecated(forRemoval = true, since = "2.410")
protected HistoryWidget createHistoryWidget() {
- return new HistoryWidget(this, getBuilds(), HISTORY_ADAPTER);
+ throw new IllegalStateException("HistoryWidget is now created via WidgetFactory implementation");
}
public static final HistoryWidget.Adapter HISTORY_ADAPTER = new Adapter<>() {
diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java
index b0c32f212a09..94d8b9683ed7 100644
--- a/core/src/main/java/hudson/model/Label.java
+++ b/core/src/main/java/hudson/model/Label.java
@@ -454,7 +454,7 @@ public Api getApi() {
public abstract V accept(LabelVisitor visitor, P param);
/**
- * Lists up all the atoms contained in in this label.
+ * Lists all the atoms contained in this label.
*
* @since 1.420
*/
diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java
index 0df916a19b5a..e3775143730a 100644
--- a/core/src/main/java/hudson/model/View.java
+++ b/core/src/main/java/hudson/model/View.java
@@ -98,6 +98,8 @@
import net.sf.json.JSONObject;
import org.jenkins.ui.icon.Icon;
import org.jenkins.ui.icon.IconSet;
+import org.jenkins.ui.symbol.Symbol;
+import org.jenkins.ui.symbol.SymbolRequest;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.stapler.DataBoundSetter;
@@ -801,11 +803,20 @@ public Categories doItemCategories(StaplerRequest req, StaplerResponse rsp, @Que
String iconClassName = descriptor.getIconClassName();
if (iconClassName != null && !iconClassName.isBlank()) {
metadata.put("iconClassName", iconClassName);
- if (resUrl != null) {
- Icon icon = IconSet.icons
- .getIconByClassSpec(String.join(" ", iconClassName, iconStyle));
- if (icon != null) {
- metadata.put("iconQualifiedUrl", icon.getQualifiedUrl(resUrl));
+ if (iconClassName.startsWith("symbol-")) {
+ String iconXml = Symbol.get(new SymbolRequest.Builder()
+ .withName(iconClassName.split(" ")[0].substring(7))
+ .withPluginName(Functions.extractPluginNameFromIconSrc(iconClassName))
+ .withClasses("icon-xlg")
+ .build());
+ metadata.put("iconXml", iconXml);
+ } else {
+ if (resUrl != null) {
+ Icon icon = IconSet.icons
+ .getIconByClassSpec(String.join(" ", iconClassName, iconStyle));
+ if (icon != null) {
+ metadata.put("iconQualifiedUrl", icon.getQualifiedUrl(resUrl));
+ }
}
}
}
diff --git a/core/src/main/java/hudson/model/WorkspaceCleanupThread.java b/core/src/main/java/hudson/model/WorkspaceCleanupThread.java
index 00ef65c4a92e..8159f1f20cc1 100644
--- a/core/src/main/java/hudson/model/WorkspaceCleanupThread.java
+++ b/core/src/main/java/hudson/model/WorkspaceCleanupThread.java
@@ -24,6 +24,8 @@
package hudson.model;
+import static hudson.Util.fileToPath;
+
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
@@ -31,12 +33,19 @@
import hudson.FilePath;
import hudson.Functions;
import hudson.Util;
+import hudson.remoting.VirtualChannel;
+import hudson.slaves.WorkspaceList;
+import java.io.File;
+import java.io.FileFilter;
import java.io.IOException;
+import java.io.Serializable;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
+import jenkins.MasterToSlaveFileCallable;
import jenkins.model.Jenkins;
import jenkins.model.ModifiableTopLevelItemGroup;
import jenkins.util.SystemProperties;
@@ -90,8 +99,7 @@ public static void invoke() {
if (check) {
listener.getLogger().println("Deleting " + ws + " on " + node.getDisplayName());
try {
- ws.deleteSuffixesRecursive();
- ws.deleteRecursive();
+ ws.act(new CleanupOldWorkspaces(retainForDays));
} catch (IOException | InterruptedException x) {
Functions.printStackTrace(x, listener.error("Failed to delete " + ws + " on " + node.getDisplayName()));
}
@@ -101,21 +109,6 @@ public static void invoke() {
}
private boolean shouldBeDeleted(@NonNull TopLevelItem item, FilePath dir, @NonNull Node n) throws IOException, InterruptedException {
- // TODO: the use of remoting is not optimal.
- // One remoting can execute "exists", "lastModified", and "delete" all at once.
- // (Could even invert master loop so that one FileCallable takes care of all known items.)
- if (!dir.exists()) {
- LOGGER.log(Level.FINE, "Directory {0} does not exist", dir);
- return false;
- }
-
- // if younger than a month, keep it
- long now = new Date().getTime();
- if (dir.lastModified() + retainForDays * DAY > now) {
- LOGGER.log(Level.FINE, "Directory {0} is only {1} old, so not deleting", new Object[] {dir, Util.getTimeSpanString(now - dir.lastModified())});
- return false;
- }
-
// TODO could also be good to add checkbox that lets users configure a workspace to never be auto-cleaned.
// TODO check instead for SCMTriggerItem:
@@ -143,11 +136,69 @@ private boolean shouldBeDeleted(@NonNull TopLevelItem item, FilePath dir, @NonNu
return false;
}
}
-
- LOGGER.log(Level.FINER, "Going to delete directory {0}", dir);
return true;
}
+ private static class CleanupOldWorkspaces extends MasterToSlaveFileCallable {
+
+ private final int retentionInDays;
+
+ CleanupOldWorkspaces(int retentionInDays) {
+ this.retentionInDays = retentionInDays;
+ }
+
+ @Override
+ public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
+ File[] workspaces = null;
+ File parentWs = f.getParentFile();
+ if (parentWs != null) {
+ workspaces = parentWs.listFiles(new ShouldBeDeletedFilter(this.retentionInDays, f.getName()));
+ }
+
+ if (workspaces != null) {
+ for (File workspace : workspaces) {
+ LOGGER.log(Level.FINER, "Going to delete directory {0}", workspace);
+ Util.deleteRecursive(fileToPath(workspace), Path::toFile);
+ }
+ }
+ return null;
+ }
+ }
+
+ private static class ShouldBeDeletedFilter implements FileFilter, Serializable {
+
+ private final int retentionInDays;
+
+ private final String workspaceBaseName;
+
+ ShouldBeDeletedFilter(int retentionInDays, String workspaceBaseName) {
+ this.retentionInDays = retentionInDays;
+ this.workspaceBaseName = workspaceBaseName;
+ }
+
+ @Override
+ public boolean accept(File dir) {
+
+ if (!dir.isDirectory()) {
+ return false;
+ }
+
+ // if not the workspace or a workspace suffix
+ if (!dir.getName().equals(workspaceBaseName) && !dir.getName().startsWith(workspaceBaseName + WorkspaceList.COMBINATOR)) {
+ return false;
+ }
+
+ // if younger than a month, keep it
+ long now = new Date().getTime();
+ if (dir.lastModified() + this.retentionInDays * DAY > now) {
+ LOGGER.log(Level.FINE, "Directory {0} is only {1} old, so not deleting", new Object[] {dir, Util.getTimeSpanString(now - dir.lastModified())});
+ return false;
+ }
+
+ return true;
+ }
+ }
+
private static final Logger LOGGER = Logger.getLogger(WorkspaceCleanupThread.class.getName());
/**
diff --git a/core/src/main/java/hudson/model/queue/MappingWorksheet.java b/core/src/main/java/hudson/model/queue/MappingWorksheet.java
index 54829ec0dac2..9dbeaa3816f4 100644
--- a/core/src/main/java/hudson/model/queue/MappingWorksheet.java
+++ b/core/src/main/java/hudson/model/queue/MappingWorksheet.java
@@ -47,6 +47,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.logging.Logger;
/**
* Defines a mapping problem for answering "where do we execute this task?"
@@ -85,6 +86,9 @@
* @author Kohsuke Kawaguchi
*/
public class MappingWorksheet {
+
+ private static final Logger LOGGER = Logger.getLogger(MappingWorksheet.class.getName());
+
public final List executors;
public final List works;
/**
@@ -135,8 +139,10 @@ public boolean canAccept(WorkChunk c) {
if (c.assignedLabel != null && !c.assignedLabel.contains(node))
return false; // label mismatch
- if (!(Node.SKIP_BUILD_CHECK_ON_FLYWEIGHTS && item.task instanceof Queue.FlyweightTask) && !nodeAcl.hasPermission2(item.authenticate2(), Computer.BUILD))
- return false; // tasks don't have a permission to run on this node
+ if (!(Node.SKIP_BUILD_CHECK_ON_FLYWEIGHTS && item.task instanceof Queue.FlyweightTask) && !nodeAcl.hasPermission2(item.authenticate2(), Computer.BUILD)) {
+ LOGGER.fine(() -> "Agent/Build permission denied to " + item.authenticate2().getName() + " on " + node.getNodeName());
+ return false;
+ }
return true;
}
diff --git a/core/src/main/java/hudson/search/FixedSet.java b/core/src/main/java/hudson/search/FixedSet.java
index 3c3819f03bcb..f77e20623542 100644
--- a/core/src/main/java/hudson/search/FixedSet.java
+++ b/core/src/main/java/hudson/search/FixedSet.java
@@ -27,6 +27,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Locale;
/**
* Set of {@link SearchItem}s that are statically known upfront.
@@ -61,7 +62,7 @@ public void suggest(String token, List result) {
boolean caseInsensitive = UserSearchProperty.isCaseInsensitive();
for (SearchItem i : items) {
String name = i.getSearchName();
- if (name != null && (name.contains(token) || (caseInsensitive && name.toLowerCase().contains(token.toLowerCase())))) {
+ if (name != null && (name.contains(token) || (caseInsensitive && name.toLowerCase(Locale.ROOT).contains(token.toLowerCase(Locale.ROOT))))) {
result.add(i);
}
}
diff --git a/core/src/main/java/hudson/slaves/JNLPLauncher.java b/core/src/main/java/hudson/slaves/JNLPLauncher.java
index 9add9faf0c3f..4f0cbc1e6153 100644
--- a/core/src/main/java/hudson/slaves/JNLPLauncher.java
+++ b/core/src/main/java/hudson/slaves/JNLPLauncher.java
@@ -175,7 +175,7 @@ public String getTunnel() {
*/
@DataBoundSetter
public void setTunnel(String tunnel) {
- this.tunnel = tunnel;
+ this.tunnel = Util.fixEmptyAndTrim(tunnel);
}
@Override
diff --git a/core/src/main/java/hudson/util/MaskingClassLoader.java b/core/src/main/java/hudson/util/MaskingClassLoader.java
index 74bbbe1a9863..dfad754fc170 100644
--- a/core/src/main/java/hudson/util/MaskingClassLoader.java
+++ b/core/src/main/java/hudson/util/MaskingClassLoader.java
@@ -59,7 +59,7 @@ public MaskingClassLoader(ClassLoader parent, String... masks) {
}
public MaskingClassLoader(ClassLoader parent, Collection masks) {
- super(parent);
+ super("Masking ClassLoader of " + parent.getName(), parent);
this.masksClasses = List.copyOf(masks);
/*
diff --git a/core/src/main/java/hudson/util/MultipartFormDataParser.java b/core/src/main/java/hudson/util/MultipartFormDataParser.java
index 8633a77e1534..f173cff9802b 100644
--- a/core/src/main/java/hudson/util/MultipartFormDataParser.java
+++ b/core/src/main/java/hudson/util/MultipartFormDataParser.java
@@ -27,17 +27,21 @@
import edu.umd.cs.findbugs.annotations.CheckForNull;
import java.io.File;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
-import org.apache.commons.fileupload.FileCountLimitExceededException;
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.fileupload.FileUploadBase;
-import org.apache.commons.fileupload.FileUploadException;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
-import org.apache.commons.fileupload.servlet.ServletFileUpload;
+import org.apache.commons.fileupload2.core.DiskFileItem;
+import org.apache.commons.fileupload2.core.DiskFileItemFactory;
+import org.apache.commons.fileupload2.core.FileItem;
+import org.apache.commons.fileupload2.core.FileUploadByteCountLimitException;
+import org.apache.commons.fileupload2.core.FileUploadException;
+import org.apache.commons.fileupload2.core.FileUploadFileCountLimitException;
+import org.apache.commons.fileupload2.core.FileUploadSizeException;
+import org.apache.commons.fileupload2.javax.JavaxServletDiskFileUpload;
+import org.apache.commons.fileupload2.javax.JavaxServletFileUpload;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -52,7 +56,7 @@ public class MultipartFormDataParser implements AutoCloseable {
/**
* Limits the number of form fields that can be processed in one multipart/form-data request.
- * Used to set {@link org.apache.commons.fileupload.servlet.ServletFileUpload#setFileCountMax(long)}.
+ * Used to set {@link org.apache.commons.fileupload2.javax.JavaxServletFileUpload#setFileCountMax(long)}.
* Despite the name, this applies to all form fields, not just actual file attachments.
* Set to {@code -1} to disable limits.
*/
@@ -60,7 +64,7 @@ public class MultipartFormDataParser implements AutoCloseable {
/**
* Limits the size (in bytes) of individual fields that can be processed in one multipart/form-data request.
- * Used to set {@link org.apache.commons.fileupload.servlet.ServletFileUpload#setFileSizeMax(long)}.
+ * Used to set {@link org.apache.commons.fileupload2.javax.JavaxServletFileUpload#setFileSizeMax(long)}.
* Despite the name, this applies to all form fields, not just actual file attachments.
* Set to {@code -1} to disable limits.
*/
@@ -68,7 +72,7 @@ public class MultipartFormDataParser implements AutoCloseable {
/**
* Limits the total request size (in bytes) that can be processed in one multipart/form-data request.
- * Used to set {@link org.apache.commons.fileupload.servlet.ServletFileUpload#setSizeMax(long)}.
+ * Used to set {@link org.apache.commons.fileupload2.javax.JavaxServletFileUpload#setSizeMax(long)}.
* Set to {@code -1} to disable limits.
*/
private static /* nonfinal for Jenkins script console */ long FILEUPLOAD_MAX_SIZE = Long.getLong(MultipartFormDataParser.class.getName() + ".FILEUPLOAD_MAX_SIZE", -1);
@@ -82,20 +86,20 @@ public MultipartFormDataParser(HttpServletRequest request, int maxParts, long ma
throw new ServletException("Error creating temporary directory", e);
}
tmpDir.deleteOnExit();
- ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, tmpDir));
+ JavaxServletFileUpload upload = new JavaxServletDiskFileUpload(DiskFileItemFactory.builder().setFile(tmpDir).get());
upload.setFileCountMax(maxParts);
upload.setFileSizeMax(maxPartSize);
upload.setSizeMax(maxSize);
try {
for (FileItem fi : upload.parseRequest(request))
byName.put(fi.getFieldName(), fi);
- } catch (FileCountLimitExceededException e) {
+ } catch (FileUploadFileCountLimitException e) {
throw new ServletException("File upload field count limit exceeded. Consider setting the Java system property "
+ MultipartFormDataParser.class.getName() + ".FILEUPLOAD_MAX_FILES to a value greater than " + FILEUPLOAD_MAX_FILES + ", or to -1 to disable this limit.", e);
- } catch (FileUploadBase.FileSizeLimitExceededException e) {
+ } catch (FileUploadByteCountLimitException e) {
throw new ServletException("File upload field size limit exceeded. Consider setting the Java system property "
+ MultipartFormDataParser.class.getName() + ".FILEUPLOAD_MAX_FILE_SIZE to a value greater than " + FILEUPLOAD_MAX_FILE_SIZE + ", or to -1 to disable this limit.", e);
- } catch (FileUploadBase.SizeLimitExceededException e) {
+ } catch (FileUploadSizeException e) {
throw new ServletException("File upload total size limit exceeded. Consider setting the Java system property "
+ MultipartFormDataParser.class.getName() + ".FILEUPLOAD_MAX_SIZE to a value greater than " + FILEUPLOAD_MAX_SIZE + ", or to -1 to disable this limit.", e);
} catch (FileUploadException e) {
@@ -118,17 +122,30 @@ public String get(String key) {
return fi.getString();
}
- public FileItem getFileItem(String key) {
+ public FileItem getFileItem2(String key) {
return byName.get(key);
}
+ /**
+ * @deprecated use {@link #getFileItem2(String)}
+ */
+ @Deprecated
+ public org.apache.commons.fileupload.FileItem getFileItem(String key) {
+ return org.apache.commons.fileupload.FileItem.fromFileUpload2FileItem(getFileItem2(key));
+ }
+
/**
* If any file is created on the disk, delete them all.
* Even if this method is not called, the resource will be still cleaned up later by GC.
*/
public void cleanUp() {
- for (FileItem item : byName.values())
- item.delete();
+ for (FileItem item : byName.values()) {
+ try {
+ item.delete();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
}
/** Alias for {@link #cleanUp}. */
diff --git a/core/src/main/java/hudson/util/StackedAreaRenderer2.java b/core/src/main/java/hudson/util/StackedAreaRenderer2.java
index 3d165b27cce9..19fefcd65d9b 100644
--- a/core/src/main/java/hudson/util/StackedAreaRenderer2.java
+++ b/core/src/main/java/hudson/util/StackedAreaRenderer2.java
@@ -109,7 +109,7 @@ public void drawItem(Graphics2D g2,
double value = dataValue.doubleValue();
- // leave the y values (y1, y0) untranslated as it is going to be be
+ // leave the y values (y1, y0) untranslated as it is going to be
// stacked up later by previous series values, after this it will be
// translated.
double xx1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
diff --git a/core/src/main/java/jenkins/agents/CloudSet.java b/core/src/main/java/jenkins/agents/CloudSet.java
index e69a3f9add3c..addefceb83ff 100644
--- a/core/src/main/java/jenkins/agents/CloudSet.java
+++ b/core/src/main/java/jenkins/agents/CloudSet.java
@@ -242,9 +242,13 @@ private void handleNewCloudPage(Descriptor descriptor, String name, Stapl
*/
@POST
public synchronized void doDoCreate(StaplerRequest req, StaplerResponse rsp,
- @QueryParameter String type) throws IOException, ServletException, Descriptor.FormException {
+ @QueryParameter String cloudDescriptorName) throws IOException, ServletException, Descriptor.FormException {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
- Cloud cloud = Cloud.all().find(type).newInstance(req, req.getSubmittedForm());
+ Descriptor cloudDescriptor = Cloud.all().findByName(cloudDescriptorName);
+ if (cloudDescriptor == null) {
+ throw new Failure(String.format("No cloud type ‘%s’ is known", cloudDescriptorName));
+ }
+ Cloud cloud = cloudDescriptor.newInstance(req, req.getSubmittedForm());
if (!Jenkins.get().clouds.add(cloud)) {
LOGGER.log(Level.WARNING, () -> "Creating duplicate cloud name " + cloud.name + ". Plugin " + Jenkins.get().getPluginManager().whichPlugin(cloud.getClass()) + " should be updated to support user provided name.");
}
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index 0808d2e7ce7f..c7453fffb0f9 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -2897,13 +2897,16 @@ public ExtensionList getExtensionList(String extensionType) throws ClassNotFound
*/
public void refreshExtensions() throws ExtensionRefreshException {
ExtensionList finders = getExtensionList(ExtensionFinder.class);
+ LOGGER.finer(() -> "refreshExtensions " + finders);
for (ExtensionFinder ef : finders) {
if (!ef.isRefreshable())
throw new ExtensionRefreshException(ef + " doesn't support refresh");
}
List fragments = new ArrayList<>();
+
for (ExtensionFinder ef : finders) {
+ LOGGER.finer(() -> "searching " + ef);
fragments.add(ef.refresh());
}
ExtensionComponentSet delta = ExtensionComponentSet.union(fragments).filtered();
@@ -2912,12 +2915,21 @@ public void refreshExtensions() throws ExtensionRefreshException {
List> newFinders = new ArrayList<>(delta.find(ExtensionFinder.class));
while (!newFinders.isEmpty()) {
ExtensionFinder f = newFinders.remove(newFinders.size() - 1).getInstance();
+ LOGGER.finer(() -> "found new ExtensionFinder " + f);
ExtensionComponentSet ecs = ExtensionComponentSet.allOf(f).filtered();
newFinders.addAll(ecs.find(ExtensionFinder.class));
delta = ExtensionComponentSet.union(delta, ecs);
}
+ // we may not have found a new Extension finder but we may be using an extension finder that is extensible
+ // e.g. hudson.ExtensionFinder.GuiceFinder is extensible by GuiceExtensionAnnotation which is done by the variant plugin
+ // so lets give it one more chance.
+ for (ExtensionFinder ef : finders) {
+ LOGGER.finer(() -> "searching again in " + ef);
+ delta = ExtensionComponentSet.union(delta, ef.refresh().filtered());
+ }
+
for (ExtensionList el : extensionLists.values()) {
el.refresh(delta);
}
@@ -4480,7 +4492,7 @@ public void doDoFingerprintCheck(StaplerRequest req, StaplerResponse rsp) throws
rsp.sendError(HttpServletResponse.SC_FORBIDDEN, "No crumb found");
}
rsp.sendRedirect2(req.getContextPath() + "/fingerprint/" +
- Util.getDigestOf(p.getFileItem("name").getInputStream()) + '/');
+ Util.getDigestOf(p.getFileItem2("name").getInputStream()) + '/');
}
}
diff --git a/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java b/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java
index 570048c5327f..203ea976bccd 100644
--- a/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java
+++ b/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java
@@ -39,7 +39,6 @@
import hudson.model.RunMap;
import hudson.model.listeners.ItemListener;
import hudson.model.queue.SubTask;
-import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
import java.io.File;
import java.io.IOException;
@@ -297,10 +296,11 @@ public List getEstimatedDurationCandidates() {
}
/**
- * Suitable for {@link Job#createHistoryWidget}.
+ * @deprecated Remove any code calling this method, history widget is now created via {@link jenkins.widgets.WidgetFactory} implementation.
*/
+ @Deprecated(forRemoval = true, since = "TODO")
public final HistoryWidget createHistoryWidget() {
- return new BuildHistoryWidget(asJob(), builds, Job.HISTORY_ADAPTER);
+ throw new IllegalStateException("HistoryWidget is now created via WidgetFactory implementation");
}
/**
diff --git a/core/src/main/java/jenkins/security/BasicHeaderProcessor.java b/core/src/main/java/jenkins/security/BasicHeaderProcessor.java
index 18ea7b5174c0..45b5eb0c70a2 100644
--- a/core/src/main/java/jenkins/security/BasicHeaderProcessor.java
+++ b/core/src/main/java/jenkins/security/BasicHeaderProcessor.java
@@ -9,6 +9,7 @@
import hudson.util.Scrambler;
import java.io.IOException;
import java.util.List;
+import java.util.Locale;
import java.util.logging.Logger;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -64,7 +65,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
HttpServletResponse rsp = (HttpServletResponse) response;
String authorization = req.getHeader("Authorization");
- if (authorization != null && authorization.toLowerCase().startsWith("Basic ".toLowerCase())) {
+ if (authorization != null && authorization.toLowerCase(Locale.ROOT).startsWith("Basic ".toLowerCase(Locale.ROOT))) {
// authenticate the user
String uidpassword = Scrambler.descramble(authorization.substring(6));
int idx = uidpassword.indexOf(':');
diff --git a/core/src/main/java/jenkins/util/URLClassLoader2.java b/core/src/main/java/jenkins/util/URLClassLoader2.java
index 72611bc49432..3fdedcbeada0 100644
--- a/core/src/main/java/jenkins/util/URLClassLoader2.java
+++ b/core/src/main/java/jenkins/util/URLClassLoader2.java
@@ -17,14 +17,43 @@ public class URLClassLoader2 extends URLClassLoader implements JenkinsClassLoade
registerAsParallelCapable();
}
+ /**
+ * @deprecated use {@link URLClassLoader2#URLClassLoader2(String, URL[])}
+ */
+ @Deprecated(since = "TODO")
public URLClassLoader2(URL[] urls) {
super(urls);
}
+ /**
+ * @deprecated use {@link URLClassLoader2#URLClassLoader2(String, URL[], ClassLoader)}
+ */
+ @Deprecated(since = "TODO")
public URLClassLoader2(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
+ /**
+ * Create a new {@link URLClassLoader2} with the given name and URLS and the {@link #getSystemClassLoader()} as its parent.
+ * @param name name of this classloader.
+ * @param urls the list of URLS to find classes in.
+ * @since TODO
+ */
+ public URLClassLoader2(String name, URL[] urls) {
+ super(name, urls, getSystemClassLoader());
+ }
+
+ /**
+ * Create a new {@link URLClassLoader2} with the given name, URLS parent.
+ * @param name name of this classloader.
+ * @param urls the list of URLS to find classes in.
+ * @param parent the parent to search for classes before we look in the {@code urls}
+ * @since TODO
+ */
+ public URLClassLoader2(String name, URL[] urls, ClassLoader parent) {
+ super(name, urls, parent);
+ }
+
@Override
public void addURL(URL url) {
super.addURL(url);
diff --git a/core/src/main/java/org/jenkins/ui/symbol/Symbol.java b/core/src/main/java/org/jenkins/ui/symbol/Symbol.java
index 9973acc18da5..9a07556d7c71 100644
--- a/core/src/main/java/org/jenkins/ui/symbol/Symbol.java
+++ b/core/src/main/java/org/jenkins/ui/symbol/Symbol.java
@@ -8,13 +8,30 @@
import hudson.Util;
import java.io.IOException;
import java.io.InputStream;
+import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
import jenkins.model.Jenkins;
import org.apache.commons.io.IOUtils;
+import org.apache.tools.ant.filters.StringInputStream;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
/**
* Helper class to load symbols from Jenkins core or plugins.
@@ -88,12 +105,37 @@ private static String loadSymbol(String namespace, String name) {
LOGGER.log(Level.FINE, "Failed to load symbol " + name, e);
}
}
- return markup.replaceAll("().*?()", "$1$2")
- .replaceAll("