diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 456f8fca6da7..a2ed88450df5 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -28,16 +28,17 @@ For refactoring and code cleanup changes, exercise the code before and after the
### Proposed changelog entries
-- JENKINS-XXXXX, human-readable text
+- human-readable text
@@ -45,6 +46,11 @@ You may add multiple changelog entries if applicable by adding a new entry to th
N/A
+
+
```[tasklist]
### Submitter checklist
- [ ] The Jira issue, if it exists, is well-described.
diff --git a/.github/workflows/label-conflicting-pr.yml b/.github/workflows/label-conflicting-pr.yml
index ae90f55bc77d..8b78edd6004c 100644
--- a/.github/workflows/label-conflicting-pr.yml
+++ b/.github/workflows/label-conflicting-pr.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Label conflicting PRs
- uses: eps1lon/actions-label-merge-conflict@v3.0.1
+ uses: eps1lon/actions-label-merge-conflict@v3.0.2
with:
dirtyLabel: "unresolved-merge-conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/publish-release-artifact.yml b/.github/workflows/publish-release-artifact.yml
index 31854bf8266f..ae5e6bf92602 100644
--- a/.github/workflows/publish-release-artifact.yml
+++ b/.github/workflows/publish-release-artifact.yml
@@ -73,7 +73,7 @@ jobs:
wget -q https://get.jenkins.io/${REPO}/${PROJECT_VERSION}/${FILE_NAME}
- name: Upload Release Asset
id: upload-war
- uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87
+ uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -108,7 +108,7 @@ jobs:
- name: Upload Release Asset
id: upload-deb
if: always()
- uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87
+ uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -144,7 +144,7 @@ jobs:
- name: Upload Release Asset
id: upload-rpm
if: always()
- uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87
+ uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -180,7 +180,7 @@ jobs:
- name: Upload Release Asset
id: upload-msi
if: always()
- uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87
+ uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -216,7 +216,7 @@ jobs:
- name: Upload Release Asset
id: upload-suse-rpm
if: always()
- uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87
+ uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
diff --git a/.gitpod/Dockerfile b/.gitpod/Dockerfile
index f2475612b107..63c4aff7d91a 100644
--- a/.gitpod/Dockerfile
+++ b/.gitpod/Dockerfile
@@ -1,6 +1,6 @@
FROM gitpod/workspace-full
-ARG MAVEN_VERSION=3.9.7
+ARG MAVEN_VERSION=3.9.8
RUN brew install gh && \
bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh && sdk install maven ${MAVEN_VERSION} && sdk default maven ${MAVEN_VERSION}"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 77c012b44c68..58af2c65852e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,7 +9,7 @@ This page provides information about contributing code to the Jenkins core codeb
1. Fork the repository on GitHub
2. Clone the forked repository to your machine
3. Install the necessary development tools. In order to develop Jenkins, you need the following:
- - Java Development Kit (JDK) 11, 17 or 21.
+ - Java Development Kit (JDK) 17 or 21.
In the Jenkins project we usually use [Eclipse Temurin](https://adoptium.net/) or [OpenJDK](https://openjdk.java.net/), but you can use other JDKs as well.
- Apache Maven 3.8.1 or above. You can [download Maven here](https://maven.apache.org/download.cgi).
In the Jenkins project we usually use the most recent Maven release.
diff --git a/Jenkinsfile b/Jenkinsfile
index 8b87983f43fc..2380b3b66d9a 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -14,12 +14,12 @@ properties([
def axes = [
platforms: ['linux', 'windows'],
- jdks: [11, 17, 21],
+ jdks: [17, 21],
]
stage('Record build') {
retry(conditions: [kubernetesAgent(handleNonKubernetes: true), nonresumable()], count: 2) {
- node('maven-11') {
+ node('maven-17') {
infra.checkoutSCM()
/*
diff --git a/ath.sh b/ath.sh
index 3ab8d159b66e..81106967e54b 100644
--- a/ath.sh
+++ b/ath.sh
@@ -6,7 +6,7 @@ set -o xtrace
cd "$(dirname "$0")"
# https://github.com/jenkinsci/acceptance-test-harness/releases
-export ATH_VERSION=5858.v4b_c4e3b_16099
+export ATH_VERSION=5909.vc4fa_3e55b_f84
if [[ $# -eq 0 ]]; then
export JDK=17
diff --git a/bom/pom.xml b/bom/pom.xml
index 273355a332b2..8f8e6412f7b0 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -40,7 +40,7 @@ THE SOFTWARE.
2.0.0-M22.0.13
- 1870.v48cc46ef5fee
+ 1892.v73465f3d074d2.4.21
@@ -56,7 +56,7 @@ THE SOFTWARE.
org.springframeworkspring-framework-bom
- 5.3.36
+ 5.3.37pomimport
@@ -64,7 +64,7 @@ THE SOFTWARE.
org.springframework.securityspring-security-bom
- 5.8.12
+ 5.8.13pomimport
@@ -82,7 +82,7 @@ THE SOFTWARE.
com.google.guavaguava
- 33.2.0-jre
+ 33.2.1-jre
@@ -113,7 +113,7 @@ THE SOFTWARE.
commons-codeccommons-codec
- 1.17.0
+ 1.17.1commons-collections
@@ -376,7 +376,7 @@ THE SOFTWARE.
commons-loggingcommons-logging
- 1.3.2
+ 1.3.3provided
diff --git a/cli/pom.xml b/cli/pom.xml
index 14d6323ca762..c037ed4a0ee8 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -119,7 +119,7 @@
org.apache.maven.pluginsmaven-shade-plugin
- 3.5.3
+ 3.6.0
diff --git a/core/pom.xml b/core/pom.xml
index 61c48e489794..95c289164930 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -507,6 +507,62 @@ THE SOFTWARE.
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+
+
+
+ enforce-banned-dependencies
+
+ enforce
+
+
+
+
+
+
+ com.fasterxml.jackson.*
+ com.github.ben-manes.caffeine:caffeine
+ com.github.jnr:jnr-posix
+ com.github.mwiede:jsch
+ com.google.code.gson:gson
+ com.jayway.jsonpath:json-path
+ commons-httpclient:commons-httpclient
+ com.sun.activation:javax.activation
+ com.sun.mail:javax.mail
+ com.sun.xml.bind:jaxb-impl
+ io.jsonwebtoken
+
+ jakarta.activation:jakarta.activation-api:*:jar:compile
+ jakarta.activation:jakarta.activation-api:*:jar:runtime
+ jakarta.mail:jakarta.mail-api
+ javax.activation:javax.activation-api
+ javax.mail:javax.mail-api
+ javax.xml.bind:jaxb-api
+ joda-time:joda-time
+
+ net.bytebuddy:byte-buddy:*:jar:compile
+ net.bytebuddy:byte-buddy:*:jar:runtime
+ net.i2p.crypto:eddsa
+ net.minidev
+ org.apache.commons:commons-lang3
+ org.apache.commons:commons-text
+ org.apache.httpcomponents
+ org.bouncycastle
+ org.eclipse.angus:angus-activation
+ org.eclipse.angus:angus-mail
+ org.glassfish.jersey.*
+ org.json:json
+ org.ow2.asm
+ org.yaml:snakeyaml
+
+
+
+
+
+
+ org.codehaus.mojobuild-helper-maven-plugin
diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java
index d9b37f0fac29..3d6edf832f9f 100644
--- a/core/src/main/java/hudson/ClassicPluginStrategy.java
+++ b/core/src/main/java/hudson/ClassicPluginStrategy.java
@@ -252,7 +252,7 @@ private void fix(Attributes atts, List optionalDepende
for (Dependency d : DetachedPluginsUtil.getImpliedDependencies(pluginName, jenkinsVersion)) {
LOGGER.fine(() -> "implied dep " + pluginName + " → " + d.shortName);
- pluginManager.considerDetachedPlugin(d.shortName);
+ pluginManager.considerDetachedPlugin(d.shortName, pluginName);
optionalDependencies.add(d);
}
}
@@ -269,17 +269,17 @@ public static List getImpliedDependencies(String plugi
}
/**
- * @deprecated since TODO use {@link #createClassLoader(String, List, ClassLoader, Attributes)}
+ * @deprecated since 2.459 use {@link #createClassLoader(String, List, ClassLoader, Attributes)}
*/
- @Deprecated(since = "TODO")
+ @Deprecated(since = "2.459")
protected ClassLoader createClassLoader(List paths, ClassLoader parent) throws IOException {
return createClassLoader(paths, parent, null);
}
/**
- * @deprecated since TODO use {@link #createClassLoader(String, List, ClassLoader, Attributes)}
+ * @deprecated since 2.459 use {@link #createClassLoader(String, List, ClassLoader, Attributes)}
*/
- @Deprecated(since="TODO")
+ @Deprecated(since="2.459")
protected ClassLoader createClassLoader(List paths, ClassLoader parent, Attributes atts) throws IOException {
// generate a legacy id so at least we can track to something
return createClassLoader("unidentified-" + UUID.randomUUID(), paths, parent, atts);
@@ -287,7 +287,7 @@ protected ClassLoader createClassLoader(List paths, ClassLoader parent, At
/**
* Creates a classloader that can load all the specified jar files and delegate to the given parent.
- * @since TODO
+ * @since 2.459
*/
protected ClassLoader createClassLoader(String name, List paths, ClassLoader parent, Attributes atts) throws IOException {
boolean usePluginFirstClassLoader =
diff --git a/core/src/main/java/hudson/DependencyRunner.java b/core/src/main/java/hudson/DependencyRunner.java
index b7db91fb9416..f577440659cf 100644
--- a/core/src/main/java/hudson/DependencyRunner.java
+++ b/core/src/main/java/hudson/DependencyRunner.java
@@ -58,7 +58,7 @@ public void run() {
// Get all top-level projects
LOGGER.fine("assembling top level projects");
for (AbstractProject p : Jenkins.get().allItems(AbstractProject.class))
- if (p.getUpstreamProjects().size() == 0) {
+ if (p.getUpstreamProjects().isEmpty()) {
LOGGER.fine("adding top level project " + p.getName());
topLevelProjects.add(p);
} else {
diff --git a/core/src/main/java/hudson/EnvVars.java b/core/src/main/java/hudson/EnvVars.java
index 286151e46bf7..97def5f11a7f 100644
--- a/core/src/main/java/hudson/EnvVars.java
+++ b/core/src/main/java/hudson/EnvVars.java
@@ -114,8 +114,7 @@ public EnvVars(@NonNull Map m) {
// because of the backward compatibility, some parts of Jenkins passes
// EnvVars as Map so downcasting is safer.
- if (m instanceof EnvVars) {
- EnvVars lhs = (EnvVars) m;
+ if (m instanceof EnvVars lhs) {
this.platform = lhs.platform;
}
}
diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java
index 44a39b22836f..06773dd9a9ee 100644
--- a/core/src/main/java/hudson/FilePath.java
+++ b/core/src/main/java/hudson/FilePath.java
@@ -321,7 +321,7 @@ public static String normalize(@NonNull String path) {
buf.append(m.group(1));
path = path.substring(m.end());
}
- boolean isAbsolute = buf.length() > 0;
+ boolean isAbsolute = !buf.isEmpty();
// Split remaining path into tokens, trimming any duplicate or trailing separators
List tokens = new ArrayList<>();
int s = 0, end = path.length();
@@ -366,7 +366,7 @@ public static String normalize(@NonNull String path) {
}
// Recombine tokens
for (String token : tokens) buf.append(token);
- if (buf.length() == 0) buf.append('.');
+ if (buf.isEmpty()) buf.append('.');
return buf.toString();
}
@@ -997,8 +997,7 @@ private boolean installIfNecessaryFrom(@NonNull URL archive, @NonNull TaskListen
}
}
- if (con instanceof HttpURLConnection) {
- HttpURLConnection httpCon = (HttpURLConnection) con;
+ if (con instanceof HttpURLConnection httpCon) {
int responseCode = httpCon.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
|| responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
@@ -1445,7 +1444,7 @@ private static class DeleteSuffixesRecursive extends MasterToSlaveFileCallable path.toFile());
+ Util.deleteRecursive(file.toPath(), Path::toFile);
}
}
@@ -1476,7 +1475,7 @@ private static class DeleteRecursive extends MasterToSlaveFileCallable {
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
- Util.deleteRecursive(fileToPath(f), path -> path.toFile());
+ Util.deleteRecursive(fileToPath(f), Path::toFile);
return null;
}
}
@@ -1493,7 +1492,7 @@ private static class DeleteContents extends MasterToSlaveFileCallable {
@Override
public Void invoke(File f, VirtualChannel channel) throws IOException {
- Util.deleteContentsRecursive(fileToPath(f), path -> path.toFile());
+ Util.deleteContentsRecursive(fileToPath(f), Path::toFile);
return null;
}
}
diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java
index 2e6abc978540..cd25f9bc9871 100644
--- a/core/src/main/java/hudson/Functions.java
+++ b/core/src/main/java/hudson/Functions.java
@@ -316,8 +316,7 @@ public static void initPageVariables(JellyContext context) {
*/
public static Class getTypeParameter(Class extends B> c, Class base, int n) {
Type parameterization = Types.getBaseClass(c, base);
- if (parameterization instanceof ParameterizedType) {
- ParameterizedType pt = (ParameterizedType) parameterization;
+ if (parameterization instanceof ParameterizedType pt) {
return Types.erasure(Types.getTypeArgument(pt, n));
} else {
throw new AssertionError(c + " doesn't properly parameterize " + base);
@@ -1422,7 +1421,7 @@ public static String getRelativeNameFrom(@CheckForNull Item p, @CheckForNull Ite
StringBuilder buf = new StringBuilder();
Item i = p;
while (true) {
- if (buf.length() > 0) buf.insert(0, separationString);
+ if (!buf.isEmpty()) buf.insert(0, separationString);
buf.insert(0, useDisplayName ? i.getDisplayName() : i.getName());
ItemGroup gr = i.getParent();
@@ -1873,7 +1872,7 @@ public static String joinPath(String... components) {
for (String s : components) {
if (s.isEmpty()) continue;
- if (buf.length() > 0) {
+ if (!buf.isEmpty()) {
if (buf.charAt(buf.length() - 1) != '/')
buf.append('/');
if (s.charAt(0) == '/') s = s.substring(1);
@@ -1979,8 +1978,7 @@ public String getServerName() {
@Deprecated
public String getCheckUrl(String userDefined, Object descriptor, String field) {
if (userDefined != null || field == null) return userDefined;
- if (descriptor instanceof Descriptor) {
- Descriptor d = (Descriptor) descriptor;
+ if (descriptor instanceof Descriptor d) {
return d.getCheckUrl(field);
}
return null;
@@ -1993,8 +1991,7 @@ public String getCheckUrl(String userDefined, Object descriptor, String field) {
public void calcCheckUrl(Map attributes, String userDefined, Object descriptor, String field) {
if (userDefined != null || field == null) return;
- if (descriptor instanceof Descriptor) {
- Descriptor d = (Descriptor) descriptor;
+ if (descriptor instanceof Descriptor d) {
CheckMethod m = d.getCheckMethod(field);
attributes.put("checkUrl", m.toStemUrl());
attributes.put("checkDependsOn", m.getDependsOn());
@@ -2057,7 +2054,7 @@ public static List> getCloudDescriptors() {
* Prepend a prefix only when there's the specified body.
*/
public String prepend(String prefix, String body) {
- if (body != null && body.length() > 0)
+ if (body != null && !body.isEmpty())
return prefix + body;
return body;
}
diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java
index 5d80c6e623e4..75dd52c4d7b7 100644
--- a/core/src/main/java/hudson/PluginManager.java
+++ b/core/src/main/java/hudson/PluginManager.java
@@ -615,7 +615,7 @@ public void run(Reactor reactor) throws Exception {
}});
}
- void considerDetachedPlugin(String shortName) {
+ void considerDetachedPlugin(String shortName, String source) {
if (new File(rootDir, shortName + ".jpi").isFile() ||
new File(rootDir, shortName + ".hpi").isFile() ||
new File(rootDir, shortName + ".jpl").isFile() ||
@@ -627,7 +627,7 @@ void considerDetachedPlugin(String shortName) {
for (String loadedFile : loadPluginsFromWar(getDetachedLocation(), (dir, name) -> normalisePluginName(name).equals(shortName))) {
String loaded = normalisePluginName(loadedFile);
File arc = new File(rootDir, loaded + ".jpi");
- LOGGER.info(() -> "Loading a detached plugin as a dependency: " + arc);
+ LOGGER.info(() -> "Loading a detached plugin " + arc + " as a dependency of " + source);
try {
plugins.add(strategy.createPluginWrapper(arc));
} catch (IOException e) {
@@ -716,6 +716,10 @@ protected static void addDependencies(URL hpiResUrl, String fromPath, Set d
}
Manifest manifest = parsePluginManifest(hpiResUrl);
+ if (manifest == null) {
+ return;
+ }
+
String dependencySpec = manifest.getMainAttributes().getValue("Plugin-Dependencies");
if (dependencySpec != null) {
String[] dependencyTokens = dependencySpec.split(",");
diff --git a/core/src/main/java/hudson/cli/CLIAction.java b/core/src/main/java/hudson/cli/CLIAction.java
index 4265a6283a2b..9e29b141560c 100644
--- a/core/src/main/java/hudson/cli/CLIAction.java
+++ b/core/src/main/java/hudson/cli/CLIAction.java
@@ -217,7 +217,7 @@ protected void closed(int statusCode, String reason) {
@Override
public Object getTarget() {
StaplerRequest req = Stapler.getCurrentRequest();
- if (req.getRestOfPath().length() == 0 && "POST".equals(req.getMethod())) {
+ if (req.getRestOfPath().isEmpty() && "POST".equals(req.getMethod())) {
// CLI connection request
if ("false".equals(req.getParameter("remoting"))) {
throw new PlainCliEndpointResponse();
diff --git a/core/src/main/java/hudson/cli/GroovyshCommand.java b/core/src/main/java/hudson/cli/GroovyshCommand.java
index 8bc4d522e254..18cbc6da7513 100644
--- a/core/src/main/java/hudson/cli/GroovyshCommand.java
+++ b/core/src/main/java/hudson/cli/GroovyshCommand.java
@@ -77,7 +77,7 @@ protected int run() {
StringBuilder commandLine = new StringBuilder();
for (String arg : args) {
- if (commandLine.length() > 0) {
+ if (!commandLine.isEmpty()) {
commandLine.append(" ");
}
commandLine.append(arg);
diff --git a/core/src/main/java/hudson/console/ExpandableDetailsNote.java b/core/src/main/java/hudson/console/ExpandableDetailsNote.java
index 1f558e6a260b..beb623b5034c 100644
--- a/core/src/main/java/hudson/console/ExpandableDetailsNote.java
+++ b/core/src/main/java/hudson/console/ExpandableDetailsNote.java
@@ -26,8 +26,8 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
-import hudson.Functions;
import hudson.MarkupText;
+import hudson.Util;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -53,7 +53,8 @@ public ExpandableDetailsNote(String caption, String html) {
@Override
public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) {
text.addMarkup(charPos,
- "
" + html + "
");
+ "
" + html + "
");
return null;
}
diff --git a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java
index b389221f763e..e2bfa4678fc9 100644
--- a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java
+++ b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java
@@ -218,7 +218,7 @@ public static void report(Saveable obj, Collection errors) {
buf.append(e.getClass().getSimpleName()).append(": ").append(e.getMessage());
}
}
- if (buf.length() == 0) return;
+ if (buf.isEmpty()) return;
Jenkins j = Jenkins.getInstanceOrNull();
if (j == null) { // Need this path, at least for unit tests, but also in case of very broken startup
// Startup failed, something is very broken, so report what we can.
diff --git a/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java b/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java
index 8873925847dc..c305e9a6febc 100644
--- a/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java
+++ b/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java
@@ -18,8 +18,8 @@
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.UncaughtExceptionFilter;
import org.kohsuke.stapler.WebApp;
-import org.kohsuke.stapler.compression.CompressionFilter;
/**
* Deals with exceptions that get thrown all the way up to the Stapler rendering layer.
@@ -30,7 +30,7 @@ public class InstallUncaughtExceptionHandler {
@Initializer
public static void init(final Jenkins j) throws IOException {
- CompressionFilter.setUncaughtExceptionHandler(j.servletContext, (e, context, req, rsp) -> handleException(j, e, req, rsp, 500));
+ UncaughtExceptionFilter.setUncaughtExceptionHandler(j.servletContext, (e, context, req, rsp) -> handleException(j, e, req, rsp, 500));
try {
Thread.setDefaultUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler());
LOGGER.log(Level.FINE, "Successfully installed a global UncaughtExceptionHandler.");
diff --git a/core/src/main/java/hudson/lifecycle/ExitLifecycle.java b/core/src/main/java/hudson/lifecycle/ExitLifecycle.java
index f8fcc3abefbf..038dafc442a3 100644
--- a/core/src/main/java/hudson/lifecycle/ExitLifecycle.java
+++ b/core/src/main/java/hudson/lifecycle/ExitLifecycle.java
@@ -26,6 +26,7 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
+import hudson.util.BootFailure;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
@@ -72,4 +73,9 @@ public void restart() {
System.exit(exitOnRestart);
}
+
+ @Override
+ public void onBootFailure(BootFailure problem) {
+ restart();
+ }
}
diff --git a/core/src/main/java/hudson/lifecycle/Lifecycle.java b/core/src/main/java/hudson/lifecycle/Lifecycle.java
index dbc53d2b5005..fcd7769aeff2 100644
--- a/core/src/main/java/hudson/lifecycle/Lifecycle.java
+++ b/core/src/main/java/hudson/lifecycle/Lifecycle.java
@@ -32,6 +32,8 @@
import hudson.Util;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
+import hudson.util.BootFailure;
+import hudson.util.JenkinsReloadFailed;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -324,6 +326,14 @@ public boolean supportsDynamicLoad() {
return true;
}
+ /**
+ * Called when Jenkins has failed to boot.
+ * @param problem a boot failure (could be {@link JenkinsReloadFailed})
+ * @since TODO
+ */
+ public void onBootFailure(BootFailure problem) {
+ }
+
@Restricted(NoExternalUse.class)
public static final class PlaceholderLifecycle extends ExitLifecycle {
diff --git a/core/src/main/java/hudson/logging/LogRecorder.java b/core/src/main/java/hudson/logging/LogRecorder.java
index 23ebc72cde6a..86aea6575048 100644
--- a/core/src/main/java/hudson/logging/LogRecorder.java
+++ b/core/src/main/java/hudson/logging/LogRecorder.java
@@ -393,7 +393,7 @@ private static final class SetLevel extends MasterToSlaveCallable {
void broadcast() {
for (Computer c : Jenkins.get().getComputers()) {
- if (c.getName().length() > 0) { // i.e. not master
+ if (!c.getName().isEmpty()) { // i.e. not master
VirtualChannel ch = c.getChannel();
if (ch != null) {
try {
@@ -595,7 +595,7 @@ public int compare(Computer c1, Computer c2) {
}
});
for (Computer c : Jenkins.get().getComputers()) {
- if (c.getName().length() == 0) {
+ if (c.getName().isEmpty()) {
continue; // master
}
List recs = new ArrayList<>();
diff --git a/core/src/main/java/hudson/model/AbstractBuild.java b/core/src/main/java/hudson/model/AbstractBuild.java
index bcd29082ebe1..85d62df78896 100644
--- a/core/src/main/java/hudson/model/AbstractBuild.java
+++ b/core/src/main/java/hudson/model/AbstractBuild.java
@@ -607,8 +607,7 @@ protected Launcher createLauncher(@NonNull BuildListener listener) throws IOExce
final Node currentNode = getCurrentNode();
Launcher l = currentNode.createLauncher(listener);
- if (project instanceof BuildableItemWithBuildWrappers) {
- BuildableItemWithBuildWrappers biwbw = (BuildableItemWithBuildWrappers) project;
+ if (project instanceof BuildableItemWithBuildWrappers biwbw) {
for (BuildWrapper bw : biwbw.getBuildWrappersList())
l = bw.decorateLauncher(AbstractBuild.this, l, listener);
}
diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java
index 5bd1d6a02d79..adebec8f289f 100644
--- a/core/src/main/java/hudson/model/AbstractItem.java
+++ b/core/src/main/java/hudson/model/AbstractItem.java
@@ -25,7 +25,6 @@
package hudson.model;
-import static hudson.model.queue.Executables.getParentOf;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
@@ -39,9 +38,6 @@
import hudson.cli.declarative.CLIResolver;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.SaveableListener;
-import hudson.model.queue.SubTask;
-import hudson.model.queue.Tasks;
-import hudson.model.queue.WorkUnit;
import hudson.security.ACL;
import hudson.security.ACLContext;
import hudson.security.AccessControlled;
@@ -57,12 +53,8 @@
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
@@ -551,8 +543,7 @@ public final String getUrl() {
List ancestors = req.getAncestors();
if (!ancestors.isEmpty()) {
Ancestor last = ancestors.get(ancestors.size() - 1);
- if (last.getObject() instanceof View) {
- View view = (View) last.getObject();
+ if (last.getObject() instanceof View view) {
if (view.getOwner().getItemGroup() == getParent() && !view.isDefault()) {
// Showing something inside a view, so should use that as the base URL.
String prefix = req.getContextPath() + "/";
@@ -706,11 +697,13 @@ public void delete(StaplerRequest req, StaplerResponse rsp) throws IOException,
*
*
* Any exception indicates the deletion has failed, but {@link AbortException} would prevent the caller
- * from showing the stack trace. This
+ * from showing the stack trace.
+ * @see ItemDeletion
*/
@Override
public void delete() throws IOException, InterruptedException {
checkPermission(DELETE);
+ ItemListener.checkBeforeDelete(this);
boolean responsibleForAbortingBuilds = !ItemDeletion.contains(this);
boolean ownsRegistration = ItemDeletion.register(this);
if (!ownsRegistration && ItemDeletion.isRegistered(this)) {
@@ -720,87 +713,7 @@ public void delete() throws IOException, InterruptedException {
try {
// if a build is in progress. Cancel it.
if (responsibleForAbortingBuilds || ownsRegistration) {
- Queue queue = Queue.getInstance();
- if (this instanceof Queue.Task) {
- // clear any items in the queue so they do not get picked up
- queue.cancel((Queue.Task) this);
- }
- // now cancel any child items - this happens after ItemDeletion registration, so we can use a snapshot
- for (Queue.Item i : queue.getItems()) {
- Item item = Tasks.getItemOf(i.task);
- while (item != null) {
- if (item == this) {
- if (!queue.cancel(i)) {
- LOGGER.warning(() -> "failed to cancel " + i);
- }
- break;
- }
- if (item.getParent() instanceof Item) {
- item = (Item) item.getParent();
- } else {
- break;
- }
- }
- }
- // interrupt any builds in progress (and this should be a recursive test so that folders do not pay
- // the 15 second delay for every child item). This happens after queue cancellation, so will be
- // a complete set of builds in flight
- Map buildsInProgress = new LinkedHashMap<>();
- for (Computer c : Jenkins.get().getComputers()) {
- for (Executor e : c.getAllExecutors()) {
- final WorkUnit workUnit = e.getCurrentWorkUnit();
- final Queue.Executable executable = workUnit != null ? workUnit.getExecutable() : null;
- final SubTask subtask = executable != null ? getParentOf(executable) : null;
-
- if (subtask != null) {
- Item item = Tasks.getItemOf(subtask);
- while (item != null) {
- if (item == this) {
- buildsInProgress.put(e, e.getCurrentExecutable());
- e.interrupt(Result.ABORTED);
- break;
- }
- if (item.getParent() instanceof Item) {
- item = (Item) item.getParent();
- } else {
- break;
- }
- }
- }
- }
- }
- if (!buildsInProgress.isEmpty()) {
- // give them 15 seconds or so to respond to the interrupt
- long expiration = System.nanoTime() + TimeUnit.SECONDS.toNanos(15);
- // comparison with executor.getCurrentExecutable() == computation currently should always be true
- // as we no longer recycle Executors, but safer to future-proof in case we ever revisit recycling
- while (!buildsInProgress.isEmpty() && expiration - System.nanoTime() > 0L) {
- // we know that ItemDeletion will prevent any new builds in the queue
- // ItemDeletion happens-before Queue.cancel so we know that the Queue will stay clear
- // Queue.cancel happens-before collecting the buildsInProgress list
- // thus buildsInProgress contains the complete set we need to interrupt and wait for
- for (Iterator> iterator =
- buildsInProgress.entrySet().iterator();
- iterator.hasNext(); ) {
- Map.Entry entry = iterator.next();
- // comparison with executor.getCurrentExecutable() == executable currently should always be
- // true as we no longer recycle Executors, but safer to future-proof in case we ever
- // revisit recycling.
- if (!entry.getKey().isAlive()
- || entry.getValue() != entry.getKey().getCurrentExecutable()) {
- iterator.remove();
- }
- // I don't know why, but we have to keep interrupting
- entry.getKey().interrupt(Result.ABORTED);
- }
- Thread.sleep(50L);
- }
- if (!buildsInProgress.isEmpty()) {
- throw new Failure(Messages.AbstractItem_FailureToStopBuilds(
- buildsInProgress.size(), getFullDisplayName()
- ));
- }
- }
+ ItemDeletion.cancelBuildsInProgress(this);
}
if (this instanceof ItemGroup) {
// delete individual items first
diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index 5de1775b11cd..c568f8df8b31 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -518,8 +518,7 @@ private AbstractBuild getBuildForDeprecatedMethods() {
Executor e = Executor.currentExecutor();
if (e != null) {
Executable exe = e.getCurrentExecutable();
- if (exe instanceof AbstractBuild) {
- AbstractBuild b = (AbstractBuild) exe;
+ if (exe instanceof AbstractBuild b) {
if (b.getProject() == this)
return b;
}
@@ -1012,6 +1011,7 @@ public List getActions() {
* null if no information is available (for example,
* if no build was done yet.)
*/
+ @SuppressWarnings("deprecation")
@Override
public Node getLastBuiltOn() {
// where was it built on?
diff --git a/core/src/main/java/hudson/model/AdministrativeMonitor.java b/core/src/main/java/hudson/model/AdministrativeMonitor.java
index 7a9a0dde970c..bdbfb48027d3 100644
--- a/core/src/main/java/hudson/model/AdministrativeMonitor.java
+++ b/core/src/main/java/hudson/model/AdministrativeMonitor.java
@@ -183,7 +183,7 @@ public void doDisable(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
/**
* Required permission to view this admin monitor.
- * By default {@link Jenkins#ADMINISTER}, but {@link Jenkins#SYSTEM_READ} is also supported.
+ * By default {@link Jenkins#ADMINISTER}, but {@link Jenkins#SYSTEM_READ} or {@link Jenkins#MANAGE} are also supported.
*
* Changing this permission check to return {@link Jenkins#SYSTEM_READ} will make the active
* administrative monitor appear on {@code manage.jelly} and on the globally visible
@@ -191,23 +191,69 @@ public void doDisable(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
* {@link #doDisable(StaplerRequest, StaplerResponse)} will still always require Administer permission.
*
*
+ * This method only allows for a single permission to be returned. If more complex permission checks are required,
+ * override {@link #checkRequiredPermission()} and {@link #hasRequiredPermission()} instead.
+ *
+ *
* Implementers need to ensure that {@code doAct} and other web methods perform necessary permission checks:
* Users with System Read permissions are expected to be limited to read-only access.
* Form UI elements that change system state, e.g. toggling a feature on or off, need to be hidden from users
* lacking Administer permission.
*
+ * @since 2.233
+ * @deprecated Callers should use {@link #checkRequiredPermission()} or {@link #hasRequiredPermission()}.
*/
+ @Deprecated
public Permission getRequiredPermission() {
return Jenkins.ADMINISTER;
}
+ /**
+ * Checks if the current user has the minimum required permission to view this administrative monitor.
+ *
+ * Subclasses may override this method and {@link #hasRequiredPermission()} instead of {@link #getRequiredPermission()} to perform more complex permission checks,
+ * for example, checking either {@link Jenkins#MANAGE} or {@link Jenkins#SYSTEM_READ}.
+ *
+ * @see #getRequiredPermission()
+ * @see #hasRequiredPermission()
+ * @since 2.468
+ */
+ public void checkRequiredPermission() {
+ Jenkins.get().checkPermission(getRequiredPermission());
+ }
+
+ /**
+ * Checks if the current user has the minimum required permission to view this administrative monitor.
+ *
+ * Subclasses may override this method and {@link #checkRequiredPermission} instead of {@link #getRequiredPermission()} to perform more complex permission checks,
+ * for example, checking either {@link Jenkins#MANAGE} or {@link Jenkins#SYSTEM_READ}.
+ *
+ * @see #getRequiredPermission()
+ * @see #checkRequiredPermission()
+ * @since 2.468
+ */
+ public boolean hasRequiredPermission() {
+ return Jenkins.get().hasPermission(getRequiredPermission());
+ }
+
+ /**
+ * Checks if the current user has the minimum required permission to view any administrative monitor.
+ *
+ * @return true if the current user has the minimum required permission to view any administrative monitor.
+ *
+ * @since 2.468
+ */
+ public static boolean hasPermissionToDisplay() {
+ return Jenkins.get().hasAnyPermission(Jenkins.SYSTEM_READ, Jenkins.MANAGE);
+ }
+
/**
* Ensure that URLs in this administrative monitor are only accessible to users with {@link #getRequiredPermission()}.
*/
@Override
@Restricted(NoExternalUse.class)
public Object getTarget() {
- Jenkins.get().checkPermission(getRequiredPermission());
+ checkRequiredPermission();
return this;
}
diff --git a/core/src/main/java/hudson/model/Api.java b/core/src/main/java/hudson/model/Api.java
index 11db76c3581d..23e72072112f 100644
--- a/core/src/main/java/hudson/model/Api.java
+++ b/core/src/main/java/hudson/model/Api.java
@@ -190,8 +190,7 @@ public void doXml(StaplerRequest req, StaplerResponse rsp,
return;
}
- // switch to gzipped output
- try (OutputStream o = rsp.getCompressedOutputStream(req)) {
+ try (OutputStream o = rsp.getOutputStream()) {
if (isSimpleOutput(result)) {
// simple output allowed
rsp.setContentType("text/plain;charset=UTF-8");
diff --git a/core/src/main/java/hudson/model/BooleanParameterDefinition.java b/core/src/main/java/hudson/model/BooleanParameterDefinition.java
index 5e270ef46da1..6e9db32216fd 100644
--- a/core/src/main/java/hudson/model/BooleanParameterDefinition.java
+++ b/core/src/main/java/hudson/model/BooleanParameterDefinition.java
@@ -59,8 +59,7 @@ public BooleanParameterDefinition(@NonNull String name, boolean defaultValue, @C
@Override
public ParameterDefinition copyWithDefaultValue(ParameterValue defaultValue) {
- if (defaultValue instanceof BooleanParameterValue) {
- BooleanParameterValue value = (BooleanParameterValue) defaultValue;
+ if (defaultValue instanceof BooleanParameterValue value) {
return new BooleanParameterDefinition(getName(), value.value, getDescription());
} else {
return this;
diff --git a/core/src/main/java/hudson/model/BuildAuthorizationToken.java b/core/src/main/java/hudson/model/BuildAuthorizationToken.java
index f101eb3d6e74..a09ed113e1cf 100644
--- a/core/src/main/java/hudson/model/BuildAuthorizationToken.java
+++ b/core/src/main/java/hudson/model/BuildAuthorizationToken.java
@@ -29,7 +29,6 @@
import hudson.security.ACL;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
-import jenkins.security.ApiTokenProperty;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
@@ -82,10 +81,6 @@ public static void checkPermission(Job, ?> project, BuildAuthorizationToken to
return;
}
- if (req.getAttribute(ApiTokenProperty.class.getName()) instanceof User) {
- return;
- }
-
rsp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
rsp.addHeader("Allow", "POST");
throw HttpResponses.forwardToView(project, "requirePOST.jelly");
diff --git a/core/src/main/java/hudson/model/CauseAction.java b/core/src/main/java/hudson/model/CauseAction.java
index eca42ffb6999..316cf3a182d6 100644
--- a/core/src/main/java/hudson/model/CauseAction.java
+++ b/core/src/main/java/hudson/model/CauseAction.java
@@ -61,8 +61,7 @@ public CauseAction(Cause c) {
private void addCause(Cause c) {
synchronized (causeBag) {
- Integer cnt = causeBag.get(c);
- causeBag.put(c, cnt == null ? 1 : cnt + 1);
+ causeBag.compute(c, (unused, cnt) -> cnt == null ? 1 : cnt + 1);
}
}
diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java
index 6df0b65354a1..38af6b5a56b3 100644
--- a/core/src/main/java/hudson/model/Computer.java
+++ b/core/src/main/java/hudson/model/Computer.java
@@ -1519,7 +1519,7 @@ public void doDumpExportTable(StaplerRequest req, StaplerResponse rsp) throws IO
checkPermission(Jenkins.ADMINISTER);
rsp.setContentType("text/plain");
- try (PrintWriter w = new PrintWriter(rsp.getCompressedWriter(req))) {
+ try (PrintWriter w = new PrintWriter(rsp.getWriter())) {
VirtualChannel vc = getChannel();
if (vc instanceof Channel) {
w.println("Controller to agent");
diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java
index 817115c3ad17..b5eb07784d27 100644
--- a/core/src/main/java/hudson/model/Descriptor.java
+++ b/core/src/main/java/hudson/model/Descriptor.java
@@ -294,8 +294,7 @@ protected Descriptor() {
// detect an type error
Type bt = Types.getBaseClass(getClass(), Descriptor.class);
- if (bt instanceof ParameterizedType) {
- ParameterizedType pt = (ParameterizedType) bt;
+ if (bt instanceof ParameterizedType pt) {
// this 't' is the closest approximation of T of Descriptor.
Class t = Types.erasure(pt.getActualTypeArguments()[0]);
if (!t.isAssignableFrom(clazz))
@@ -595,6 +594,9 @@ public T newInstance(@Nullable StaplerRequest req, @NonNull JSONObject formData)
return verifyNewInstance(bindJSON(req, clazz, formData, true));
}
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | RuntimeException e) {
+ if (e instanceof RuntimeException && e instanceof HttpResponse) {
+ throw (RuntimeException) e;
+ }
throw new LinkageError("Failed to instantiate " + clazz + " from " + RedactSecretJsonInErrorMessageSanitizer.INSTANCE.sanitize(formData), e);
}
}
@@ -675,7 +677,7 @@ public Object instantiate(Class actualType, JSONObject json) {
+ actualType.getName() + " " + json);
}
} catch (Exception x) {
- LOGGER.log(Level.WARNING, "falling back to default instantiation " + actualType.getName() + " " + json, x);
+ LOGGER.log(x instanceof HttpResponse ? Level.FINE : Level.WARNING, "falling back to default instantiation " + actualType.getName() + " " + json, x);
// If nested objects are not using newInstance, bindJSON will wind up throwing the same exception anyway,
// so logging above will result in a duplicated stack trace.
// However if they *are* then this is the only way to find errors in that newInstance.
@@ -688,8 +690,7 @@ public Object instantiate(Class actualType, JSONObject json) {
@Override
public Object onConvert(Type targetType, Class targetTypeErasure, Object jsonSource) {
- if (jsonSource instanceof JSONObject) {
- JSONObject json = (JSONObject) jsonSource;
+ if (jsonSource instanceof JSONObject json) {
if (isApplicable(targetTypeErasure, json)) {
LOGGER.log(Level.FINE, "switching to newInstance {0} {1}", new Object[] {targetTypeErasure.getName(), json});
try {
@@ -895,8 +896,7 @@ protected final String getViewPage(Class> clazz, String pageName) {
protected List getPossibleViewNames(String baseName) {
List names = new ArrayList<>();
for (Facet f : WebApp.get(Jenkins.get().servletContext).facets) {
- if (f instanceof JellyCompatibleFacet) {
- JellyCompatibleFacet jcf = (JellyCompatibleFacet) f;
+ if (f instanceof JellyCompatibleFacet jcf) {
for (String ext : jcf.getScriptExtensions())
names.add(baseName + ext);
}
@@ -1007,6 +1007,10 @@ public static URL getStaticHelpUrl(StaplerRequest req, Klass> c, String suffix
if (url != null) return url;
url = c.getResource(base + '_' + locale.getLanguage() + ".html");
if (url != null) return url;
+ if (locale.getLanguage().equals("en")) {
+ url = c.getResource(base + ".html");
+ if (url != null) return url;
+ }
}
// default
diff --git a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
index 8083efa72dbf..9d455f4c1bd3 100644
--- a/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
+++ b/core/src/main/java/hudson/model/DirectoryBrowserSupport.java
@@ -33,6 +33,7 @@
import java.io.OutputStream;
import java.io.Serializable;
import java.net.URL;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
@@ -90,11 +91,6 @@ public final class DirectoryBrowserSupport implements HttpResponse {
private static final Pattern TMPDIR_PATTERN = Pattern.compile(".+@tmp/.*");
- /**
- * Escape hatch for the protection against SECURITY-2481. If enabled, the absolute paths on Windows will be allowed.
- */
- static final String ALLOW_ABSOLUTE_PATH_PROPERTY_NAME = DirectoryBrowserSupport.class.getName() + ".allowAbsolutePath";
-
public final ModelObject owner;
public final String title;
@@ -230,7 +226,7 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
String pathElement = pathTokens.nextToken();
// Treat * and ? as wildcard unless they match a literal filename
if ((pathElement.contains("?") || pathElement.contains("*"))
- && inBase && !root.child((_base.length() > 0 ? _base + "/" : "") + pathElement).exists())
+ && inBase && !root.child((!_base.isEmpty() ? _base + "/" : "") + pathElement).exists())
inBase = false;
if (pathElement.equals("*zip*")) {
// the expected syntax is foo/bar/*zip*/bar.zip
@@ -245,7 +241,7 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
}
StringBuilder sb = inBase ? _base : _rest;
- if (sb.length() > 0) sb.append('/');
+ if (!sb.isEmpty()) sb.append('/');
sb.append(pathElement);
if (!inBase)
restSize++;
@@ -260,13 +256,11 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
if (base.isEmpty()) {
baseFile = root;
} else {
- if (!SystemProperties.getBoolean(ALLOW_ABSOLUTE_PATH_PROPERTY_NAME, false)) {
- boolean isAbsolute = root.run(new IsAbsolute(base));
- if (isAbsolute) {
- LOGGER.info(() -> "SECURITY-2481 The path provided in the URL (" + base + ") is absolute and thus is refused.");
- rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
- return;
- }
+ boolean isAbsolute = root.run(new IsAbsolute(base));
+ if (isAbsolute) {
+ LOGGER.info(() -> "SECURITY-2481 The path provided in the URL (" + base + ") is absolute and thus is refused.");
+ rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return;
}
baseFile = root.child(base);
}
@@ -315,7 +309,7 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
}
List> glob = null;
- boolean patternUsed = rest.length() > 0;
+ boolean patternUsed = !rest.isEmpty();
boolean containsSymlink = false;
boolean containsTmpDir = false;
if (patternUsed) {
@@ -530,7 +524,7 @@ private static String createBackRef(int times) {
private static void zip(StaplerResponse rsp, VirtualFile root, VirtualFile dir, String glob) throws IOException, InterruptedException {
OutputStream outputStream = rsp.getOutputStream();
try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {
- zos.setEncoding(System.getProperty("file.encoding")); // TODO JENKINS-20663 make this overridable via query parameter
+ zos.setEncoding(Charset.defaultCharset().displayName()); // TODO JENKINS-20663 make this overridable via query parameter
// TODO consider using run(Callable) here
if (glob.isEmpty()) {
diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java
index b9a5610c4e35..0988b36785dd 100644
--- a/core/src/main/java/hudson/model/DownloadService.java
+++ b/core/src/main/java/hudson/model/DownloadService.java
@@ -404,7 +404,7 @@ public FormValidation updateNow() throws IOException {
}
jsonList.add(o);
}
- if (jsonList.size() == 0 && toolInstallerMetadataExists) {
+ if (jsonList.isEmpty() && toolInstallerMetadataExists) {
return FormValidation.warning("None of the tool installer metadata passed the signature check");
} else if (!toolInstallerMetadataExists) {
LOGGER.log(Level.WARNING, "No tool installer metadata found for " + id);
diff --git a/core/src/main/java/hudson/model/ExecutorListener.java b/core/src/main/java/hudson/model/ExecutorListener.java
index 6500e6acc2ec..beff34804d92 100644
--- a/core/src/main/java/hudson/model/ExecutorListener.java
+++ b/core/src/main/java/hudson/model/ExecutorListener.java
@@ -30,7 +30,7 @@
/**
* A listener for task related events from executors.
* A {@link Computer#getRetentionStrategy} or {@link SlaveComputer#getLauncher} may implement this interface.
- * Or you may create an implementation as an extension (since TODO).
+ * Or you may create an implementation as an extension (since 2.318).
* @author Stephen Connolly
* @since 1.312
*/
diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java
index bd7721906b5f..df482e28e03c 100644
--- a/core/src/main/java/hudson/model/Fingerprint.java
+++ b/core/src/main/java/hudson/model/Fingerprint.java
@@ -608,7 +608,7 @@ public synchronized boolean removeAll(RangeSet that) {
public synchronized String toString() {
StringBuilder buf = new StringBuilder();
for (Range r : ranges) {
- if (buf.length() > 0) buf.append(',');
+ if (!buf.isEmpty()) buf.append(',');
buf.append(r);
}
return buf.toString();
@@ -787,7 +787,7 @@ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingC
public static String serialize(RangeSet src) {
StringBuilder buf = new StringBuilder(src.ranges.size() * 10);
for (Range r : src.ranges) {
- if (buf.length() > 0) buf.append(',');
+ if (!buf.isEmpty()) buf.append(',');
if (r.isSingle())
buf.append(r.start);
else
diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java
index a1386565cfd0..fac4ae8a4a01 100644
--- a/core/src/main/java/hudson/model/Items.java
+++ b/core/src/main/java/hudson/model/Items.java
@@ -208,7 +208,7 @@ public static TopLevelItemDescriptor getDescriptor(String fqcn) {
public static String toNameList(Collection extends Item> items) {
StringBuilder buf = new StringBuilder();
for (Item item : items) {
- if (buf.length() > 0)
+ if (!buf.isEmpty())
buf.append(", ");
buf.append(item.getFullName());
}
diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java
index 92f1d374fb08..4bd28ff2e325 100644
--- a/core/src/main/java/hudson/model/Job.java
+++ b/core/src/main/java/hudson/model/Job.java
@@ -266,8 +266,7 @@ public void onCopied(Item src, Item item) {
// If any of the other ItemListeners modify the job, they effect
// a save, which will clear the holdOffBuildUntilUserSave and
// causing a regression of JENKINS-2494
- if (item instanceof Job) {
- Job job = (Job) item;
+ if (item instanceof Job job) {
synchronized (job) {
job.holdOffBuildUntilUserSave = false;
}
@@ -1110,8 +1109,7 @@ class FeedItem {
List entries = new ArrayList<>();
String scmDisplayName = "";
- if (this instanceof SCMTriggerItem) {
- SCMTriggerItem scmItem = (SCMTriggerItem) this;
+ if (this instanceof SCMTriggerItem scmItem) {
List scmNames = new ArrayList<>();
for (SCM s : scmItem.getSCMs()) {
scmNames.add(s.getDescriptor().getDisplayName());
diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java
index 94d8b9683ed7..88661c6df690 100644
--- a/core/src/main/java/hudson/model/Label.java
+++ b/core/src/main/java/hudson/model/Label.java
@@ -591,7 +591,7 @@ public Object unmarshal(HierarchicalStreamReader reader, final UnmarshallingCont
public static Set parse(@CheckForNull String labels) {
final Set r = new TreeSet<>();
labels = fixNull(labels);
- if (labels.length() > 0) {
+ if (!labels.isEmpty()) {
Jenkins j = Jenkins.get();
LabelAtom labelAtom = j.tryGetLabelAtom(labels);
if (labelAtom == null) {
diff --git a/core/src/main/java/hudson/model/ListView.java b/core/src/main/java/hudson/model/ListView.java
index 77852e994ece..d8f6a8d48bcb 100644
--- a/core/src/main/java/hudson/model/ListView.java
+++ b/core/src/main/java/hudson/model/ListView.java
@@ -359,7 +359,7 @@ public boolean isAddToCurrentView() {
private boolean needToAddToCurrentView(StaplerRequest req) throws ServletException {
String json = req.getParameter("json");
- if (json != null && json.length() > 0) {
+ if (json != null && !json.isEmpty()) {
// Submitted via UI
JSONObject form = req.getSubmittedForm();
return form.has("addToCurrentView") && form.getBoolean("addToCurrentView");
diff --git a/core/src/main/java/hudson/model/MyViewsProperty.java b/core/src/main/java/hudson/model/MyViewsProperty.java
index 9d7b8b651d1c..71809d788eae 100644
--- a/core/src/main/java/hudson/model/MyViewsProperty.java
+++ b/core/src/main/java/hudson/model/MyViewsProperty.java
@@ -29,6 +29,7 @@
import hudson.Extension;
import hudson.Util;
import hudson.model.Descriptor.FormException;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.security.ACL;
import hudson.util.FormValidation;
import hudson.views.MyViewsTabBar;
@@ -246,6 +247,11 @@ public String getDisplayName() {
public UserProperty newInstance(User user) {
return new MyViewsProperty();
}
+
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Preferences.class);
+ }
}
@Override
diff --git a/core/src/main/java/hudson/model/PaneStatusProperties.java b/core/src/main/java/hudson/model/PaneStatusProperties.java
index 29a460349440..4807020ca714 100644
--- a/core/src/main/java/hudson/model/PaneStatusProperties.java
+++ b/core/src/main/java/hudson/model/PaneStatusProperties.java
@@ -2,7 +2,9 @@
import static java.lang.String.format;
+import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.util.PersistedList;
import java.io.IOException;
import javax.servlet.http.HttpSession;
@@ -56,6 +58,10 @@ public boolean isEnabled() {
return false;
}
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Invisible.class);
+ }
}
private static class PaneStatusPropertiesSessionFallback extends PaneStatusProperties {
diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java
index d489f042100d..0d299fb9426d 100644
--- a/core/src/main/java/hudson/model/Queue.java
+++ b/core/src/main/java/hudson/model/Queue.java
@@ -130,6 +130,7 @@
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.QueryParameter;
+import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
@@ -2414,6 +2415,10 @@ public Api getApi() throws AccessDeniedException {
}
}
+ public HttpResponse doIndex(StaplerRequest req) {
+ return HttpResponses.text("Queue item exists. For details check, for example, " + req.getRequestURI() + "api/json?tree=cancelled,executable[url]");
+ }
+
protected Object readResolve() {
this.future = new FutureImpl(task);
return this;
diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java
index 324a5eff3cb9..33658252b922 100644
--- a/core/src/main/java/hudson/model/Run.java
+++ b/core/src/main/java/hudson/model/Run.java
@@ -1341,7 +1341,7 @@ public void computeDisplayName() {
private String combineLast(String[] token, int n) {
StringBuilder buf = new StringBuilder();
for (int i = Math.max(0, token.length - n); i < token.length; i++) {
- if (buf.length() > 0) buf.append('/');
+ if (!buf.isEmpty()) buf.append('/');
buf.append(token[i]);
}
return buf.toString();
@@ -2286,7 +2286,7 @@ public void doBuildTimestamp(StaplerRequest req, StaplerResponse rsp, @QueryPara
public void doConsoleText(StaplerRequest req, StaplerResponse rsp) throws IOException {
rsp.setContentType("text/plain;charset=UTF-8");
try (InputStream input = getLogInputStream();
- OutputStream os = rsp.getCompressedOutputStream(req);
+ OutputStream os = rsp.getOutputStream();
PlainTextConsoleOutputStream out = new PlainTextConsoleOutputStream(os)) {
IOUtils.copy(input, out);
}
diff --git a/core/src/main/java/hudson/model/TimeZoneProperty.java b/core/src/main/java/hudson/model/TimeZoneProperty.java
index bdf39c58527e..2675448dad80 100644
--- a/core/src/main/java/hudson/model/TimeZoneProperty.java
+++ b/core/src/main/java/hudson/model/TimeZoneProperty.java
@@ -4,6 +4,7 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.Util;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.util.ListBoxModel.Option;
@@ -106,6 +107,10 @@ public FormValidation doCheckTimeZoneName(@QueryParameter String timeZoneName) {
}
}
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Account.class);
+ }
}
@CheckForNull
diff --git a/core/src/main/java/hudson/model/TopLevelItemDescriptor.java b/core/src/main/java/hudson/model/TopLevelItemDescriptor.java
index 16aa01807ff8..d2ebe3065349 100644
--- a/core/src/main/java/hudson/model/TopLevelItemDescriptor.java
+++ b/core/src/main/java/hudson/model/TopLevelItemDescriptor.java
@@ -239,7 +239,7 @@ public String getIconClassName() {
// this one is easy... too easy... also will never happen
return IconSet.toNormalizedIconNameClass(path);
}
- if (Jenkins.RESOURCE_PATH.length() > 0 && path.startsWith(Jenkins.RESOURCE_PATH)) {
+ if (!Jenkins.RESOURCE_PATH.isEmpty() && path.startsWith(Jenkins.RESOURCE_PATH)) {
// will to live falling
path = path.substring(Jenkins.RESOURCE_PATH.length());
}
diff --git a/core/src/main/java/hudson/model/UpdateCenter.java b/core/src/main/java/hudson/model/UpdateCenter.java
index 56fd6e77c988..218cbf6b96a4 100644
--- a/core/src/main/java/hudson/model/UpdateCenter.java
+++ b/core/src/main/java/hudson/model/UpdateCenter.java
@@ -390,7 +390,7 @@ public Badge getBadge() {
if (size > 0) {
StringBuilder tooltip = new StringBuilder();
Badge.Severity severity = Badge.Severity.WARNING;
- int securityFixSize = (int) plugins.stream().filter(plugin -> plugin.fixesSecurityVulnerabilities()).count();
+ int securityFixSize = (int) plugins.stream().filter(Plugin::fixesSecurityVulnerabilities).count();
int incompatibleSize = (int) plugins.stream().filter(plugin -> !plugin.isCompatibleWithInstalledVersion()).count();
if (size > 1) {
tooltip.append(jenkins.management.Messages.PluginsLink_updatesAvailable(size));
@@ -1322,6 +1322,10 @@ public File download(DownloadJob job, URL src) throws IOException {
sha512 != null ? new DigestOutputStream(_out, sha512) : _out, sha256) : _out, sha1) : _out;
InputStream in = con.getInputStream();
CountingInputStream cin = new CountingInputStream(in)) {
+ if (LOGGER.isLoggable(Level.FINE)) {
+ var sourceUrlString = getSourceUrl(src, con);
+ LOGGER.fine(() -> "Downloading " + job.getName() + " from " + sourceUrlString);
+ }
while ((len = cin.read(buf)) >= 0) {
out.write(buf, 0, len);
final int count = cin.getCount();
@@ -1358,15 +1362,22 @@ public File download(DownloadJob job, URL src) throws IOException {
return tmp;
} catch (IOException e) {
// assist troubleshooting in case of e.g. "too many redirects" by printing actual URL
- String extraMessage = "";
- if (con != null && con.getURL() != null && !src.toString().equals(con.getURL().toString())) {
- // Two URLs are considered equal if different hosts resolve to same IP. Prefer to log in case of string inequality,
- // because who knows how the server responds to different host name in the request header?
- // Also, since it involved name resolution, it'd be an expensive operation.
- extraMessage = " (redirected to: " + con.getURL() + ")";
+ throw new IOException("Failed to download from " + getSourceUrl(src, con), e);
+ }
+ }
+
+ private static String getSourceUrl(@NonNull URL src, @CheckForNull URLConnection connection) {
+ var sourceUrlString = src.toExternalForm();
+ if (connection != null) {
+ var connectionURL = connection.getURL();
+ if (connectionURL != null) {
+ var finalUrlString = connectionURL.toExternalForm();
+ if (!sourceUrlString.equals(finalUrlString)) {
+ return sourceUrlString + " → " + finalUrlString;
+ }
}
- throw new IOException("Failed to download from " + src + extraMessage, e);
}
+ return sourceUrlString;
}
/**
diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java
index 7488a9c85b57..8a38ef2d470b 100644
--- a/core/src/main/java/hudson/model/UpdateSite.java
+++ b/core/src/main/java/hudson/model/UpdateSite.java
@@ -192,7 +192,7 @@ public long getDataTimestamp() {
@Deprecated
public @CheckForNull Future updateDirectly(final boolean signatureCheck) {
if (! getDataFile().exists() || isDue()) {
- return Jenkins.get().getUpdateCenter().updateService.submit(new Callable() {
+ return Jenkins.get().getUpdateCenter().updateService.submit(new Callable<>() {
@Override public FormValidation call() throws Exception {
return updateDirectlyNow(signatureCheck);
}
diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java
index a242385cb11a..792622eb3c54 100644
--- a/core/src/main/java/hudson/model/User.java
+++ b/core/src/main/java/hudson/model/User.java
@@ -39,13 +39,11 @@
import hudson.XmlFile;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
-import hudson.model.Descriptor.FormException;
import hudson.model.listeners.SaveableListener;
import hudson.security.ACL;
import hudson.security.AccessControlled;
import hudson.security.SecurityRealm;
import hudson.security.UserMayOrMayNotExistException2;
-import hudson.util.FormApply;
import hudson.util.FormValidation;
import hudson.util.RunList;
import hudson.util.XStream2;
@@ -77,7 +75,6 @@
import jenkins.security.LastGrantedAuthoritiesProperty;
import jenkins.security.UserDetailsCache;
import jenkins.util.SystemProperties;
-import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -87,7 +84,6 @@
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
-import org.kohsuke.stapler.verb.POST;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -342,6 +338,29 @@ public synchronized void addProperty(@NonNull UserProperty p) throws IOException
save();
}
+ /**
+ * Expand {@link #addProperty(UserProperty)} for multiple properties to be done at once.
+ * Expected to be used by the categorized configuration pages to update part of the properties.
+ * The properties not included in the list will be let untouched.
+ * It will call the {@link UserProperty#setUser(User)} method and at the end, {@link #save()} once.
+ *
+ * @since 2.468
+ */
+ public synchronized void addProperties(@NonNull List multipleProperties) throws IOException {
+ List newProperties = new ArrayList<>(this.properties);
+ for (UserProperty property : multipleProperties) {
+ UserProperty oldProp = getProperty(property.getClass());
+ if (oldProp != null) {
+ newProperties.remove(oldProp);
+ }
+ newProperties.add(property);
+ property.setUser(this);
+ }
+
+ this.properties = newProperties;
+ this.save();
+ }
+
/**
* List of all {@link UserProperty}s exposed primarily for the remoting API.
*/
@@ -859,48 +878,6 @@ public Api getApi() {
return new Api(this);
}
- /**
- * Accepts submission from the configuration page.
- */
- @POST
- public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
- checkPermission(Jenkins.ADMINISTER);
-
- JSONObject json = req.getSubmittedForm();
- String oldFullName = this.fullName;
- fullName = json.getString("fullName");
- description = json.getString("description");
-
- List props = new ArrayList<>();
- int i = 0;
- for (UserPropertyDescriptor d : UserProperty.all()) {
- UserProperty p = getProperty(d.clazz);
-
- JSONObject o = json.optJSONObject("userProperty" + i++);
- if (o != null) {
- if (p != null) {
- p = p.reconfigure(req, o);
- } else {
- p = d.newInstance(req, o);
- }
- }
-
- if (p != null) {
- p.setUser(this);
- props.add(p);
- }
- }
- this.properties = props;
-
- save();
-
- if (oldFullName != null && !oldFullName.equals(this.fullName)) {
- UserDetailsCache.get().invalidate(oldFullName);
- }
-
- FormApply.success(".").generateResponse(req, rsp, this);
- }
-
/**
* Deletes this user from Hudson.
*/
diff --git a/core/src/main/java/hudson/model/UserProperty.java b/core/src/main/java/hudson/model/UserProperty.java
index a9b9dbae7acd..a6ebeb738b23 100644
--- a/core/src/main/java/hudson/model/UserProperty.java
+++ b/core/src/main/java/hudson/model/UserProperty.java
@@ -24,9 +24,13 @@
package hudson.model;
+import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.DescriptorExtensionList;
import hudson.ExtensionPoint;
import hudson.model.Descriptor.FormException;
+import hudson.model.userproperty.UserPropertyCategory;
+import java.util.ArrayList;
+import java.util.List;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
@@ -58,6 +62,10 @@ public abstract class UserProperty implements ReconfigurableDescribable all(
return Jenkins.get().getDescriptorList(UserProperty.class);
}
+ /**
+ * Returns all the registered {@link UserPropertyCategory} descriptors for a given category.
+ *
+ * @since 2.468
+ */
+ public static List allByCategoryClass(@NonNull Class extends UserPropertyCategory> categoryClass) {
+ DescriptorExtensionList all = all();
+
+ List onlyForTheCategory = new ArrayList<>(all.size());
+ for (UserPropertyDescriptor descriptor : all) {
+ if (descriptor.getUserPropertyCategory().getClass().equals(categoryClass)) {
+ onlyForTheCategory.add(descriptor);
+ }
+ }
+
+ return onlyForTheCategory;
+ }
+
@Override
public UserProperty reconfigure(StaplerRequest req, JSONObject form) throws FormException {
return form == null ? null : getDescriptor().newInstance(req, form);
diff --git a/core/src/main/java/hudson/model/UserPropertyDescriptor.java b/core/src/main/java/hudson/model/UserPropertyDescriptor.java
index 22e05ea59aff..66762bf3c716 100644
--- a/core/src/main/java/hudson/model/UserPropertyDescriptor.java
+++ b/core/src/main/java/hudson/model/UserPropertyDescriptor.java
@@ -24,6 +24,12 @@
package hudson.model;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.model.userproperty.UserPropertyCategory;
+import java.util.Optional;
+import org.jenkinsci.Symbol;
+
/**
* {@link Descriptor} for {@link UserProperty}.
*
@@ -73,4 +79,51 @@ protected UserPropertyDescriptor() {
public boolean isEnabled() {
return true;
}
+
+ /**
+ * Define the category for this user property descriptor.
+ *
+ * @return never null, always the same value for a given instance of {@link Descriptor}.
+ *
+ * @since 2.468
+ */
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ // As this method is expected to be overloaded by subclasses
+ // the logic here is just done to support plugins with older core version
+ String categoryAsString = this.getUserPropertyCategoryAsString();
+ if (categoryAsString != null) {
+ Optional firstIfFound = UserPropertyCategory.all().stream()
+ .filter(cat -> {
+ Symbol symbolAnnotation = cat.getClass().getAnnotation(Symbol.class);
+ if (symbolAnnotation != null) {
+ for (String symbolValue : symbolAnnotation.value()) {
+ if (symbolValue.equalsIgnoreCase(categoryAsString)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ })
+ .findFirst();
+ if (firstIfFound.isPresent()) {
+ return firstIfFound.get();
+ }
+ }
+ return UserPropertyCategory.get(UserPropertyCategory.Unclassified.class);
+ }
+
+ /**
+ * Method proposed to prevent plugins to rely on too recent core version
+ * while keeping the possibility to use the categories.
+ *
+ * @deprecated This should only be used when the core requirement is below the version this method was added
+ *
+ * @return String name corresponding to the symbol of {@link #getUserPropertyCategory()}
+ *
+ * @since 2.468
+ */
+ @Deprecated
+ protected @CheckForNull String getUserPropertyCategoryAsString() {
+ return null;
+ }
}
diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java
index e3775143730a..7b1bd98eb9ee 100644
--- a/core/src/main/java/hudson/model/View.java
+++ b/core/src/main/java/hudson/model/View.java
@@ -510,8 +510,7 @@ private boolean filterQueueItemTest(Queue.Item item, Collection vi
}
}
// Check root project for sub-job projects (e.g. matrix jobs).
- if (item.task instanceof AbstractProject, ?>) {
- AbstractProject, ?> project = (AbstractProject, ?>) item.task;
+ if (item.task instanceof AbstractProject, ?> project) {
return viewItems.contains(project.getRootProject());
}
return false;
@@ -855,8 +854,7 @@ public BuildTimelineWidget getTimeline() {
public void doRssLatest(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
List lastBuilds = new ArrayList<>();
for (TopLevelItem item : getItems()) {
- if (item instanceof Job) {
- Job job = (Job) item;
+ if (item instanceof Job job) {
Run lb = job.getLastBuild();
if (lb != null) lastBuilds.add(lb);
}
diff --git a/core/src/main/java/hudson/model/listeners/ItemListener.java b/core/src/main/java/hudson/model/listeners/ItemListener.java
index aef431c3207b..fe09a9c56373 100644
--- a/core/src/main/java/hudson/model/listeners/ItemListener.java
+++ b/core/src/main/java/hudson/model/listeners/ItemListener.java
@@ -35,6 +35,8 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.util.Listeners;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Receives notifications about CRUD operations of {@link Item}.
@@ -94,6 +96,16 @@ public void onCopied(Item src, Item item) {
public void onLoaded() {
}
+ /**
+ * Called before an item is deleted, providing the ability to veto the deletion operation before it starts.
+ * @param item the item being deleted
+ * @throws Failure to veto the operation.
+ * @throws InterruptedException If a blocking condition was interrupted, also vetoing the operation.
+ * @since TODO
+ */
+ public void onCheckDelete(Item item) throws Failure, InterruptedException {
+ }
+
/**
* Called right before a job is going to be deleted.
*
@@ -205,6 +217,19 @@ public static void fireOnUpdated(final Item item) {
Listeners.notify(ItemListener.class, false, l -> l.onUpdated(item));
}
+ @Restricted(NoExternalUse.class)
+ public static void checkBeforeDelete(Item item) throws Failure, InterruptedException {
+ for (ItemListener l : all()) {
+ try {
+ l.onCheckDelete(item);
+ } catch (Failure e) {
+ throw e;
+ } catch (RuntimeException x) {
+ LOGGER.log(Level.WARNING, "failed to send event to listener of " + l.getClass(), x);
+ }
+ }
+ }
+
/** @since 1.548 */
public static void fireOnDeleted(final Item item) {
Listeners.notify(ItemListener.class, false, l -> l.onDeleted(item));
diff --git a/core/src/main/java/hudson/model/queue/MappingWorksheet.java b/core/src/main/java/hudson/model/queue/MappingWorksheet.java
index 9dbeaa3816f4..8150dd1bc132 100644
--- a/core/src/main/java/hudson/model/queue/MappingWorksheet.java
+++ b/core/src/main/java/hudson/model/queue/MappingWorksheet.java
@@ -190,7 +190,9 @@ public class WorkChunk extends ReadOnlyList {
* If the previous execution of this task run on a certain node
* and this task prefers to run on the same node, return that.
* Otherwise null.
+ * @deprecated Unused.
*/
+ @Deprecated
public final ExecutorChunk lastBuiltOn;
@@ -200,6 +202,7 @@ private WorkChunk(List base, int index) {
this.index = index;
this.assignedLabel = getAssignedLabel(base.get(0));
+ @SuppressWarnings("deprecation")
Node lbo = base.get(0).getLastBuiltOn();
for (ExecutorChunk ec : executors) {
if (ec.node == lbo) {
diff --git a/core/src/main/java/hudson/model/queue/QueueTaskFilter.java b/core/src/main/java/hudson/model/queue/QueueTaskFilter.java
index e2a113dfaa59..a36c5ca7c753 100644
--- a/core/src/main/java/hudson/model/queue/QueueTaskFilter.java
+++ b/core/src/main/java/hudson/model/queue/QueueTaskFilter.java
@@ -52,6 +52,7 @@ public Label getAssignedLabel() {
return base.getAssignedLabel();
}
+ @Deprecated
@Override
public Node getLastBuiltOn() {
return base.getLastBuiltOn();
diff --git a/core/src/main/java/hudson/model/queue/SubTask.java b/core/src/main/java/hudson/model/queue/SubTask.java
index f8b7dd435088..0690d074617c 100644
--- a/core/src/main/java/hudson/model/queue/SubTask.java
+++ b/core/src/main/java/hudson/model/queue/SubTask.java
@@ -62,7 +62,9 @@ default Label getAssignedLabel() {
* and this task prefers to run on the same node, return that.
* Otherwise null.
* @return by default, null
+ * @deprecated Unused.
*/
+ @Deprecated
default Node getLastBuiltOn() {
return null;
}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategory.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategory.java
new file mode 100644
index 000000000000..5d5467b6eed4
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategory.java
@@ -0,0 +1,204 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, 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.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.ExtensionList;
+import hudson.ExtensionPoint;
+import hudson.model.ModelObject;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.DoNotUse;
+
+/**
+ * Grouping of related {@link UserProperty}s.
+ *
+ *
+ * To facilitate the separation of the user properties into multiple pages, tabs, and so on,
+ * {@link UserProperty}s are classified into categories (such as "security", "preferences", as well
+ * as the catch-all "unclassified".) Categories themselves are extensible — plugins may introduce
+ * its own category as well, although that should only happen if you are creating a big enough subsystem.
+ *
+ * @since 2.468
+ * @see UserProperty
+ */
+public abstract class UserPropertyCategory implements ExtensionPoint, ModelObject {
+ /**
+ * One-line plain text message that explains what this category is about.
+ * This can be used in the UI to help the user pick the right category.
+ *
+ * The text should be longer than {@link #getDisplayName()}
+ */
+ public abstract String getShortDescription();
+
+ /**
+ * Returns all the registered {@link UserPropertyCategory} descriptors.
+ */
+ public static ExtensionList all() {
+ return ExtensionList.lookup(UserPropertyCategory.class);
+ }
+
+ public static @NonNull T get(Class type) {
+ T category = all().get(type);
+ if (category == null) {
+ throw new AssertionError("Category not found. It seems the " + type + " is not annotated with @Extension and so not registered");
+ }
+ return category;
+ }
+
+ /**
+ * This category is used when the {@link hudson.model.UserPropertyDescriptor} has not implemented
+ * the {@link UserPropertyDescriptor#getUserPropertyCategory()} method
+ * (or the getUserPropertyCategoryAsString method for compatibility reason).
+ *
+ * If you do not know what to use, choose the {@link Account} instead of this one.
+ */
+ @Extension
+ @Symbol("unclassified")
+ @Restricted(DoNotUse.class)
+ public static class Unclassified extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Unclassified_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Unclassified_ShortDescription();
+ }
+ }
+
+ /**
+ * User property related to account settings (e.g. timezone, email, ...).
+ *
+ * It could be seen as the default choice for {@link UserProperty} that are defining their category.
+ * Currently it has the same effect as {@link Unclassified} but the behavior could change in the future.
+ */
+ @Extension
+ @Symbol("account")
+ public static class Account extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Account_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Account_ShortDescription();
+ }
+ }
+
+ /**
+ * Preferences related configurations (e.g. notification type, default view, ...).
+ */
+ @Extension
+ @Symbol("preferences")
+ public static class Preferences extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Preferences_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Preferences_ShortDescription();
+ }
+ }
+
+ /**
+ * Per user feature flags (e.g. new design, ...).
+ */
+ @Extension
+ @Symbol("experimental")
+ public static class Experimental extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Experimental_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Experimental_ShortDescription();
+ }
+ }
+
+ /**
+ * User interface related configurations (e.g. theme, language, ...).
+ *
+ * See also {@link jenkins.appearance.AppearanceCategory}.
+ */
+ @Extension
+ @Symbol("appearance")
+ public static class Appearance extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Appearance_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Appearance_ShortDescription();
+ }
+ }
+
+
+ /**
+ * Security related configurations (e.g. API Token, SSH keys, ...).
+ * With this separation, we can more easily add control on their modifications.
+ */
+ @Extension
+ @Symbol("security")
+ public static class Security extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Security_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Security_ShortDescription();
+ }
+ }
+
+ /**
+ * For user properties that are not expected to be displayed,
+ * typically automatically configured by automated behavior, without direct user interaction.
+ */
+ @Extension
+ @Symbol("invisible")
+ public static class Invisible extends UserPropertyCategory {
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategory_Invisible_DisplayName();
+ }
+
+ @Override
+ public String getShortDescription() {
+ return Messages.UserPropertyCategory_Invisible_ShortDescription();
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAccountAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAccountAction.java
new file mode 100644
index 000000000000..822cdc6f4c99
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAccountAction.java
@@ -0,0 +1,121 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, 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.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.DescriptorExtensionList;
+import hudson.Extension;
+import hudson.model.Action;
+import hudson.model.Descriptor;
+import hudson.model.TransientUserActionFactory;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import javax.servlet.ServletException;
+import jenkins.model.Jenkins;
+import jenkins.security.UserDetailsCache;
+import net.sf.json.JSONObject;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.verb.POST;
+
+@Restricted(NoExternalUse.class)
+public class UserPropertyCategoryAccountAction extends UserPropertyCategoryAction implements Action {
+ public UserPropertyCategoryAccountAction(@NonNull User user) {
+ super(user);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategoryAccountAction_DisplayName();
+ }
+
+ @Override
+ public String getIconFileName() {
+ return getTargetUser().hasPermission(Jenkins.ADMINISTER) ? "symbol-settings" : null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "account";
+ }
+
+ public @NonNull List getMyCategoryDescriptors() {
+ return allByTwoCategoryClasses(UserPropertyCategory.Unclassified.class, UserPropertyCategory.Account.class);
+ }
+
+ private static List allByTwoCategoryClasses(
+ @NonNull Class extends UserPropertyCategory> categoryClass1,
+ @NonNull Class extends UserPropertyCategory> categoryClass2
+ ) {
+ DescriptorExtensionList all = UserProperty.all();
+
+ List filteredList = new ArrayList<>(all.size());
+ for (UserPropertyDescriptor descriptor : all) {
+ Class extends UserPropertyCategory> currClass = descriptor.getUserPropertyCategory().getClass();
+ if (currClass.equals(categoryClass1) || currClass.equals(categoryClass2)) {
+ filteredList.add(descriptor);
+ }
+ }
+
+ return filteredList;
+ }
+
+ @POST
+ public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, Descriptor.FormException {
+ User targetUser = this.getTargetUser();
+ targetUser.checkPermission(Jenkins.ADMINISTER);
+
+ JSONObject json = req.getSubmittedForm();
+
+ String oldFullName = targetUser.getFullName();
+ targetUser.setFullName(json.getString("fullName"));
+ targetUser.setDescription(json.getString("description"));
+
+ super.doConfigSubmit(req, rsp);
+
+ if (!oldFullName.equals(targetUser.getFullName())) {
+ UserDetailsCache.get().invalidate(oldFullName);
+ }
+ }
+
+ /**
+ * Inject the outer class configuration page into the sidenav and the request routing of the user
+ */
+ @Extension(ordinal = 400)
+ @Symbol("account")
+ public static class AccountActionFactory extends TransientUserActionFactory {
+ public Collection extends Action> createFor(User target) {
+ return Collections.singleton(new UserPropertyCategoryAccountAction(target));
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAction.java
new file mode 100644
index 000000000000..caec7c1bdf88
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAction.java
@@ -0,0 +1,65 @@
+package hudson.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.model.Descriptor;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import hudson.util.FormApply;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.ServletException;
+import jenkins.model.Jenkins;
+import net.sf.json.JSONObject;
+import org.kohsuke.stapler.StaplerRequest;
+import org.kohsuke.stapler.StaplerResponse;
+import org.kohsuke.stapler.verb.POST;
+
+public abstract class UserPropertyCategoryAction {
+
+ private final User targetUser;
+
+ public UserPropertyCategoryAction(User targetUser) {
+ this.targetUser = targetUser;
+ }
+
+ public @NonNull User getTargetUser() {
+ return targetUser;
+ }
+
+ public @NonNull abstract List getMyCategoryDescriptors();
+
+ @POST
+ public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, Descriptor.FormException {
+ this.targetUser.checkPermission(Jenkins.ADMINISTER);
+
+ JSONObject json = req.getSubmittedForm();
+
+ List props = new ArrayList<>();
+ List myCategoryDescriptors = getMyCategoryDescriptors();
+ int i = 0;
+ for (UserPropertyDescriptor d : myCategoryDescriptors) {
+ UserProperty p = this.targetUser.getProperty(d.clazz);
+
+ JSONObject o = json.optJSONObject("userProperty" + i++);
+ if (o != null) {
+ if (p != null) {
+ p = p.reconfigure(req, o);
+ } else {
+ p = d.newInstance(req, o);
+ }
+ }
+
+ if (p != null) {
+ props.add(p);
+ }
+ }
+ this.targetUser.addProperties(props);
+
+ this.targetUser.save();
+
+ // we are in /user///, going to /user//
+ FormApply.success("..").generateResponse(req, rsp, this);
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAppearanceAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAppearanceAction.java
new file mode 100644
index 000000000000..88d08a8add70
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryAppearanceAction.java
@@ -0,0 +1,76 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, 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.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.model.Action;
+import hudson.model.TransientUserActionFactory;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import jenkins.model.Jenkins;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Restricted(NoExternalUse.class)
+public class UserPropertyCategoryAppearanceAction extends UserPropertyCategoryAction implements Action {
+ public UserPropertyCategoryAppearanceAction(@NonNull User user) {
+ super(user);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategoryAppearanceAction_DisplayName();
+ }
+
+ @Override
+ public String getIconFileName() {
+ return getTargetUser().hasPermission(Jenkins.ADMINISTER) ? "symbol-brush-outline" : null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "appearance";
+ }
+
+ public @NonNull List getMyCategoryDescriptors() {
+ return UserProperty.allByCategoryClass(UserPropertyCategory.Appearance.class);
+ }
+
+ /**
+ * Inject the outer class configuration page into the sidenav and the request routing of the user
+ */
+ @Extension(ordinal = 350)
+ @Symbol("appearance")
+ public static class AppearanceActionFactory extends TransientUserActionFactory {
+ public Collection extends Action> createFor(User target) {
+ return Collections.singleton(new UserPropertyCategoryAppearanceAction(target));
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryExperimentalAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryExperimentalAction.java
new file mode 100644
index 000000000000..bb242bacad7a
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryExperimentalAction.java
@@ -0,0 +1,76 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, 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.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.model.Action;
+import hudson.model.TransientUserActionFactory;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import jenkins.model.Jenkins;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Restricted(NoExternalUse.class)
+public class UserPropertyCategoryExperimentalAction extends UserPropertyCategoryAction implements Action {
+ public UserPropertyCategoryExperimentalAction(@NonNull User user) {
+ super(user);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategoryExperimentalAction_DisplayName();
+ }
+
+ @Override
+ public String getIconFileName() {
+ return getTargetUser().hasPermission(Jenkins.ADMINISTER) ? "symbol-flask" : null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "experiments";
+ }
+
+ public @NonNull List getMyCategoryDescriptors() {
+ return UserProperty.allByCategoryClass(UserPropertyCategory.Experimental.class);
+ }
+
+ /**
+ * Inject the outer class configuration page into the sidenav and the request routing of the user
+ */
+ @Extension(ordinal = 100)
+ @Symbol("experimental")
+ public static class ExperimentalActionFactory extends TransientUserActionFactory {
+ public Collection extends Action> createFor(User target) {
+ return Collections.singleton(new UserPropertyCategoryExperimentalAction(target));
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryPreferencesAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryPreferencesAction.java
new file mode 100644
index 000000000000..7a74b702e6c0
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategoryPreferencesAction.java
@@ -0,0 +1,76 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, 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.model.userproperty;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.model.Action;
+import hudson.model.TransientUserActionFactory;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import jenkins.model.Jenkins;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Restricted(NoExternalUse.class)
+public class UserPropertyCategoryPreferencesAction extends UserPropertyCategoryAction implements Action {
+ public UserPropertyCategoryPreferencesAction(@NonNull User user) {
+ super(user);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategoryPreferencesAction_DisplayName();
+ }
+
+ @Override
+ public String getIconFileName() {
+ return getTargetUser().hasPermission(Jenkins.ADMINISTER) ? "symbol-parameters" : null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "preferences";
+ }
+
+ public @NonNull List getMyCategoryDescriptors() {
+ return UserProperty.allByCategoryClass(UserPropertyCategory.Preferences.class);
+ }
+
+ /**
+ * Inject the outer class configuration page into the sidenav and the request routing of the user
+ */
+ @Extension(ordinal = 300)
+ @Symbol("preferences")
+ public static class PreferencesActionFactory extends TransientUserActionFactory {
+ public Collection extends Action> createFor(User target) {
+ return Collections.singleton(new UserPropertyCategoryPreferencesAction(target));
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/model/userproperty/UserPropertyCategorySecurityAction.java b/core/src/main/java/hudson/model/userproperty/UserPropertyCategorySecurityAction.java
new file mode 100644
index 000000000000..a6cb3e6ed3c4
--- /dev/null
+++ b/core/src/main/java/hudson/model/userproperty/UserPropertyCategorySecurityAction.java
@@ -0,0 +1,77 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2022, 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.model.userproperty;
+
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+import hudson.Extension;
+import hudson.model.Action;
+import hudson.model.TransientUserActionFactory;
+import hudson.model.User;
+import hudson.model.UserProperty;
+import hudson.model.UserPropertyDescriptor;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import jenkins.model.Jenkins;
+import org.jenkinsci.Symbol;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Restricted(NoExternalUse.class)
+public class UserPropertyCategorySecurityAction extends UserPropertyCategoryAction implements Action {
+ public UserPropertyCategorySecurityAction(@NonNull User user) {
+ super(user);
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Messages.UserPropertyCategorySecurityAction_DisplayName();
+ }
+
+ @Override
+ public String getIconFileName() {
+ return getTargetUser().hasPermission(Jenkins.ADMINISTER) ? "symbol-lock-closed" : null;
+ }
+
+ @Override
+ public String getUrlName() {
+ return "security";
+ }
+
+ public @NonNull List getMyCategoryDescriptors() {
+ return UserProperty.allByCategoryClass(UserPropertyCategory.Security.class);
+ }
+
+ /**
+ * Inject the outer class configuration page into the sidenav and the request routing of the user
+ */
+ @Extension(ordinal = 200)
+ @Symbol("security")
+ public static class SecurityActionFactory extends TransientUserActionFactory {
+ public Collection extends Action> createFor(User target) {
+ return Collections.singleton(new UserPropertyCategorySecurityAction(target));
+ }
+ }
+}
diff --git a/core/src/main/java/hudson/scm/ChangeLogSet.java b/core/src/main/java/hudson/scm/ChangeLogSet.java
index a0c4a4c9cd79..f9f29720355f 100644
--- a/core/src/main/java/hudson/scm/ChangeLogSet.java
+++ b/core/src/main/java/hudson/scm/ChangeLogSet.java
@@ -238,7 +238,7 @@ public Collection extends AffectedFile> getAffectedFiles() {
ChangeLogSet parent = getParent();
if (null != parent) {
String kind = parent.getKind();
- if (null != kind && kind.trim().length() > 0) scm = kind;
+ if (null != kind && !kind.trim().isEmpty()) scm = kind;
}
throw new UnsupportedOperationException("getAffectedFiles() is not implemented by " + scm);
}
diff --git a/core/src/main/java/hudson/scm/browsers/QueryBuilder.java b/core/src/main/java/hudson/scm/browsers/QueryBuilder.java
index 06d933aa7ef3..72e6ebc0d557 100644
--- a/core/src/main/java/hudson/scm/browsers/QueryBuilder.java
+++ b/core/src/main/java/hudson/scm/browsers/QueryBuilder.java
@@ -38,7 +38,7 @@ public QueryBuilder(String s) {
public QueryBuilder add(String s) {
if (s == null) return this; // nothing to add
- if (buf.length() == 0) buf.append('?');
+ if (buf.isEmpty()) buf.append('?');
else buf.append('&');
buf.append(s);
return this;
diff --git a/core/src/main/java/hudson/search/ParsedQuickSilver.java b/core/src/main/java/hudson/search/ParsedQuickSilver.java
index dc214892ac00..879f9c080d16 100644
--- a/core/src/main/java/hudson/search/ParsedQuickSilver.java
+++ b/core/src/main/java/hudson/search/ParsedQuickSilver.java
@@ -85,7 +85,7 @@ private ParsedQuickSilver(Class extends SearchableModelObject> clazz) {
private String splitName(String url) {
StringBuilder buf = new StringBuilder(url.length() + 5);
for (String token : url.split("(?<=[a-z])(?=[A-Z])")) {
- if (buf.length() > 0) buf.append(' ');
+ if (!buf.isEmpty()) buf.append(' ');
buf.append(Introspector.decapitalize(token));
}
return buf.toString();
diff --git a/core/src/main/java/hudson/search/SuggestedItem.java b/core/src/main/java/hudson/search/SuggestedItem.java
index 86dbc36b5fe3..9ba455270e58 100644
--- a/core/src/main/java/hudson/search/SuggestedItem.java
+++ b/core/src/main/java/hudson/search/SuggestedItem.java
@@ -111,7 +111,7 @@ private void getUrl(StringBuilder buf) {
buf.setLength(0);
buf.append(f);
} else {
- if (buf.length() == 0 || buf.charAt(buf.length() - 1) != '/')
+ if (buf.isEmpty() || buf.charAt(buf.length() - 1) != '/')
buf.append('/');
buf.append(f);
}
diff --git a/core/src/main/java/hudson/search/UserSearchProperty.java b/core/src/main/java/hudson/search/UserSearchProperty.java
index 7e9836944c0b..a3515dd08874 100644
--- a/core/src/main/java/hudson/search/UserSearchProperty.java
+++ b/core/src/main/java/hudson/search/UserSearchProperty.java
@@ -5,6 +5,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.StaplerRequest;
@@ -54,6 +55,10 @@ public UserProperty newInstance(StaplerRequest req, JSONObject formData) throws
return new UserSearchProperty(formData.optBoolean("insensitiveSearch"));
}
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Preferences.class);
+ }
}
}
diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
index 6305a69a6abb..bd122244c7e2 100644
--- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
+++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java
@@ -39,6 +39,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.security.FederatedLoginService.FederatedIdentity;
import hudson.security.captcha.CaptchaSupport;
import hudson.util.FormValidation;
@@ -449,7 +450,7 @@ private SignupInfo validateAccountCreationForm(StaplerRequest req, boolean valid
si.errors.put("password1", Messages.HudsonPrivateSecurityRealm_CreateAccount_PasswordNotMatch());
}
- if (!(si.password1 != null && si.password1.length() != 0)) {
+ if (!(si.password1 != null && !si.password1.isEmpty())) {
si.errors.put("password1", Messages.HudsonPrivateSecurityRealm_CreateAccount_PasswordRequired());
}
@@ -801,7 +802,6 @@ public boolean equals(Object o) {
public int hashCode() {
return getUsername().hashCode();
}
-
}
public static class ConverterImpl extends XStream2.PassthruConverter {
@@ -884,6 +884,11 @@ public boolean isEnabled() {
public UserProperty newInstance(User user) {
return null;
}
+
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Security.class);
+ }
}
}
diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java
index 525368f65692..b969a57eb781 100644
--- a/core/src/main/java/hudson/security/SecurityRealm.java
+++ b/core/src/main/java/hudson/security/SecurityRealm.java
@@ -326,7 +326,7 @@ public void doLogout(StaplerRequest req, StaplerResponse rsp) throws IOException
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder.clearContext();
- String contextPath = req.getContextPath().length() > 0 ? req.getContextPath() : "/";
+ String contextPath = !req.getContextPath().isEmpty() ? req.getContextPath() : "/";
resetRememberMeCookie(req, rsp, contextPath);
clearStaleSessionCookies(req, rsp, contextPath);
diff --git a/core/src/main/java/hudson/security/csrf/CrumbFilter.java b/core/src/main/java/hudson/security/csrf/CrumbFilter.java
index 03e17a0fd894..8b4b558938e7 100644
--- a/core/src/main/java/hudson/security/csrf/CrumbFilter.java
+++ b/core/src/main/java/hudson/security/csrf/CrumbFilter.java
@@ -105,7 +105,7 @@ private static String canonicalPath(String path) {
buf.append(token);
}
// translation: if (path.endsWith("/") && !buf.endsWith("/"))
- if (path.endsWith("/") && (buf.length() == 0 || buf.charAt(buf.length() - 1) != '/'))
+ if (path.endsWith("/") && (buf.isEmpty() || buf.charAt(buf.length() - 1) != '/'))
buf.append('/');
return buf.toString();
}
diff --git a/core/src/main/java/hudson/security/csrf/CrumbIssuer.java b/core/src/main/java/hudson/security/csrf/CrumbIssuer.java
index 384f0a765ddd..cd0c51b03fb0 100644
--- a/core/src/main/java/hudson/security/csrf/CrumbIssuer.java
+++ b/core/src/main/java/hudson/security/csrf/CrumbIssuer.java
@@ -78,7 +78,7 @@ public String getCrumb(ServletRequest request) {
if (crumb == null) {
crumb = issueCrumb(request, getDescriptor().getCrumbSalt());
if (request != null) {
- if (crumb != null && crumb.length() > 0) {
+ if (crumb != null && !crumb.isEmpty()) {
request.setAttribute(CRUMB_ATTRIBUTE, crumb);
} else {
request.removeAttribute(CRUMB_ATTRIBUTE);
@@ -204,7 +204,7 @@ public static class RestrictedApi extends Api {
text = null;
}
if (text != null) {
- try (OutputStream o = rsp.getCompressedOutputStream(req)) {
+ try (OutputStream o = rsp.getOutputStream()) {
rsp.setContentType("text/plain;charset=UTF-8");
o.write(text.getBytes(StandardCharsets.UTF_8));
}
diff --git a/core/src/main/java/hudson/slaves/CloudRetentionStrategy.java b/core/src/main/java/hudson/slaves/CloudRetentionStrategy.java
index ec0142dd4768..e4601a2fad31 100644
--- a/core/src/main/java/hudson/slaves/CloudRetentionStrategy.java
+++ b/core/src/main/java/hudson/slaves/CloudRetentionStrategy.java
@@ -64,7 +64,7 @@ public long check(final AbstractCloudComputer c) {
}
}
}
- return 1;
+ return 0;
}
/**
diff --git a/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java b/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java
index 4e1b376caffe..a8dfeba1b20a 100644
--- a/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java
+++ b/core/src/main/java/hudson/slaves/CloudSlaveRetentionStrategy.java
@@ -37,7 +37,7 @@ public long check(T c) {
}
}
}
- return checkCycle();
+ return TimeUnit.MILLISECONDS.toMinutes(checkCycle());
}
/**
diff --git a/core/src/main/java/hudson/slaves/ComputerRetentionWork.java b/core/src/main/java/hudson/slaves/ComputerRetentionWork.java
index ddee61115a03..42256855117d 100644
--- a/core/src/main/java/hudson/slaves/ComputerRetentionWork.java
+++ b/core/src/main/java/hudson/slaves/ComputerRetentionWork.java
@@ -25,13 +25,15 @@
package hudson.slaves;
import hudson.Extension;
+import hudson.ExtensionList;
+import hudson.model.AperiodicWork;
import hudson.model.Computer;
import hudson.model.Node;
-import hudson.model.PeriodicWork;
import hudson.model.Queue;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
+import jenkins.model.GlobalComputerRetentionCheckIntervalConfiguration;
import jenkins.model.Jenkins;
import org.jenkinsci.Symbol;
@@ -42,7 +44,7 @@
* @author Stephen Connolly
*/
@Extension @Symbol("computerRetention")
-public class ComputerRetentionWork extends PeriodicWork {
+public class ComputerRetentionWork extends AperiodicWork {
/**
* Use weak hash map to avoid leaking {@link Computer}.
@@ -51,12 +53,18 @@ public class ComputerRetentionWork extends PeriodicWork {
@Override
public long getRecurrencePeriod() {
- return MIN;
+ return ExtensionList.lookupSingleton(GlobalComputerRetentionCheckIntervalConfiguration.class).getComputerRetentionCheckInterval() * 1000L;
+ }
+
+ @Override
+ public AperiodicWork getNewInstance() {
+ // ComputerRetentionWork is a singleton.
+ return this;
}
@SuppressWarnings("unchecked")
@Override
- protected void doRun() {
+ protected void doAperiodicRun() {
final long startRun = System.currentTimeMillis();
for (final Computer c : Jenkins.get().getComputers()) {
Queue.withLock(new Runnable() {
@@ -67,8 +75,7 @@ public void run() {
return;
if (!nextCheck.containsKey(c) || startRun > nextCheck.get(c)) {
// at the moment I don't trust strategies to wait more than 60 minutes
- // strategies need to wait at least one minute
- final long waitInMins = Math.max(1, Math.min(60, c.getRetentionStrategy().check(c)));
+ final long waitInMins = Math.max(0, Math.min(60, c.getRetentionStrategy().check(c)));
nextCheck.put(c, startRun + TimeUnit.MINUTES.toMillis(waitInMins));
}
}
diff --git a/core/src/main/java/hudson/slaves/RetentionStrategy.java b/core/src/main/java/hudson/slaves/RetentionStrategy.java
index e06b8e42c069..687e44d5c172 100644
--- a/core/src/main/java/hudson/slaves/RetentionStrategy.java
+++ b/core/src/main/java/hudson/slaves/RetentionStrategy.java
@@ -171,7 +171,7 @@ public Always() {
public long check(SlaveComputer c) {
if (c.isOffline() && !c.isConnecting() && c.isLaunchSupported())
c.tryReconnect();
- return 1;
+ return 0;
}
@Extension(ordinal = 100) @Symbol("always")
@@ -285,7 +285,7 @@ public long check(final SlaveComputer c) {
return TimeUnit.MILLISECONDS.toMinutes(TimeUnit.MINUTES.toMillis(idleDelay) - idleMilliseconds);
}
}
- return 1;
+ return 0;
}
@Extension @Symbol("demand")
diff --git a/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java b/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java
index ef6e3d0c8658..ea90529b29b4 100644
--- a/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java
+++ b/core/src/main/java/hudson/slaves/SimpleScheduledRetentionStrategy.java
@@ -209,7 +209,7 @@ public void run() {
LOGGER.log(INFO,
"Disabling new jobs for computer {0} as it has finished its scheduled uptime",
new Object[]{c.getName()});
- return 1;
+ return 0;
} else if (c.isIdle() && c.isAcceptingTasks()) {
Queue.withLock(new Runnable() {
@Override
@@ -243,7 +243,7 @@ public void run() {
}
}
}
- return 1;
+ return 0;
}
private synchronized boolean isOnlineScheduled() {
diff --git a/core/src/main/java/hudson/tasks/Fingerprinter.java b/core/src/main/java/hudson/tasks/Fingerprinter.java
index 3be7631bb1f0..4d843b27b003 100644
--- a/core/src/main/java/hudson/tasks/Fingerprinter.java
+++ b/core/src/main/java/hudson/tasks/Fingerprinter.java
@@ -182,7 +182,7 @@ public void perform(Run, ?> build, FilePath workspace, EnvVars environment, La
Map record = new HashMap<>();
- if (targets.length() != 0) {
+ if (!targets.isEmpty()) {
String expandedTargets = targets;
if (build instanceof AbstractBuild) { // no expansion for pipelines
expandedTargets = environment.expand(expandedTargets);
diff --git a/core/src/main/java/hudson/tools/AbstractCommandInstaller.java b/core/src/main/java/hudson/tools/AbstractCommandInstaller.java
index 46e7bc6657a2..88cbe5915cc0 100644
--- a/core/src/main/java/hudson/tools/AbstractCommandInstaller.java
+++ b/core/src/main/java/hudson/tools/AbstractCommandInstaller.java
@@ -90,7 +90,7 @@ public abstract static class Descriptor {
public FormValidation doCheckCommand(@QueryParameter String value) {
- if (value.length() > 0) {
+ if (!value.isEmpty()) {
return FormValidation.ok();
} else {
return FormValidation.error(Messages.CommandInstaller_no_command());
@@ -98,7 +98,7 @@ public FormValidation doCheckCommand(@QueryParameter String value) {
}
public FormValidation doCheckToolHome(@QueryParameter String value) {
- if (value.length() > 0) {
+ if (!value.isEmpty()) {
return FormValidation.ok();
} else {
return FormValidation.error(Messages.CommandInstaller_no_toolHome());
diff --git a/core/src/main/java/hudson/tools/InstallerTranslator.java b/core/src/main/java/hudson/tools/InstallerTranslator.java
index 35db845dd2e6..45e24cadf60d 100644
--- a/core/src/main/java/hudson/tools/InstallerTranslator.java
+++ b/core/src/main/java/hudson/tools/InstallerTranslator.java
@@ -59,11 +59,8 @@ public String getToolHome(Node node, ToolInstallation tool, TaskListener log) th
if (installer.appliesTo(node)) {
Semaphore semaphore;
synchronized (mutexByNode) {
- Map mutexByTool = mutexByNode.computeIfAbsent(node, k -> new WeakHashMap<>());
- semaphore = mutexByTool.get(tool);
- if (semaphore == null) {
- mutexByTool.put(tool, semaphore = new Semaphore(1));
- }
+ Map mutexByTool = mutexByNode.computeIfAbsent(node, unused -> new WeakHashMap<>());
+ semaphore = mutexByTool.computeIfAbsent(tool, unused -> new Semaphore(1));
}
semaphore.acquire();
try {
diff --git a/core/src/main/java/hudson/triggers/SCMTrigger.java b/core/src/main/java/hudson/triggers/SCMTrigger.java
index e35a3b66af69..43fdc6bb1062 100644
--- a/core/src/main/java/hudson/triggers/SCMTrigger.java
+++ b/core/src/main/java/hudson/triggers/SCMTrigger.java
@@ -468,7 +468,7 @@ public String getUrlName() {
*/
public void doPollingLog(StaplerRequest req, StaplerResponse rsp) throws IOException {
rsp.setContentType("text/plain;charset=UTF-8");
- try (OutputStream os = rsp.getCompressedOutputStream(req);
+ try (OutputStream os = rsp.getOutputStream();
// Prevent jelly from flushing stream so Content-Length header can be added afterwards
FlushProofOutputStream out = new FlushProofOutputStream(os)) {
getPollingLogText().writeLogTo(0, out);
diff --git a/core/src/main/java/hudson/util/ArgumentListBuilder.java b/core/src/main/java/hudson/util/ArgumentListBuilder.java
index 245f3e74a99b..9b2c7bebb594 100644
--- a/core/src/main/java/hudson/util/ArgumentListBuilder.java
+++ b/core/src/main/java/hudson/util/ArgumentListBuilder.java
@@ -295,7 +295,7 @@ public List toList() {
public String toStringWithQuote() {
StringBuilder buf = new StringBuilder();
for (String arg : args) {
- if (buf.length() > 0) buf.append(' ');
+ if (!buf.isEmpty()) buf.append(' ');
if (arg.indexOf(' ') >= 0 || arg.isEmpty())
buf.append('"').append(arg).append('"');
@@ -400,7 +400,7 @@ private static boolean startQuoting(StringBuilder buf, String arg, int atIndex)
* @return true if there are any masked arguments; false otherwise
*/
public boolean hasMaskedArguments() {
- return mask.length() > 0;
+ return !mask.isEmpty();
}
/**
@@ -437,7 +437,7 @@ public String toString() {
if (mask.get(i))
arg = "******";
- if (buf.length() > 0) buf.append(' ');
+ if (!buf.isEmpty()) buf.append(' ');
if (arg.indexOf(' ') >= 0 || arg.isEmpty())
buf.append('"').append(arg).append('"');
diff --git a/core/src/main/java/hudson/util/BootFailure.java b/core/src/main/java/hudson/util/BootFailure.java
index a460ffea148e..f23cf7fa66b7 100644
--- a/core/src/main/java/hudson/util/BootFailure.java
+++ b/core/src/main/java/hudson/util/BootFailure.java
@@ -15,6 +15,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
+import jenkins.model.Jenkins;
import jenkins.util.groovy.GroovyHookScript;
import org.kohsuke.stapler.WebApp;
@@ -51,6 +52,7 @@ public void publish(ServletContext context, @CheckForNull File home) {
.bind("servletContext", context)
.bind("attempts", loadAttempts(home))
.run();
+ Jenkins.get().getLifecycle().onBootFailure(this);
}
/**
diff --git a/core/src/main/java/hudson/util/DescribableList.java b/core/src/main/java/hudson/util/DescribableList.java
index a73dac196b2c..9c62af2f191a 100644
--- a/core/src/main/java/hudson/util/DescribableList.java
+++ b/core/src/main/java/hudson/util/DescribableList.java
@@ -219,8 +219,7 @@ public void rebuildHetero(StaplerRequest req, JSONObject formData, Collection
*/
public void buildDependencyGraph(AbstractProject owner, DependencyGraph graph) {
for (Object o : this) {
- if (o instanceof DependencyDeclarer) {
- DependencyDeclarer dd = (DependencyDeclarer) o;
+ if (o instanceof DependencyDeclarer dd) {
try {
dd.buildDependencyGraph(owner, graph);
} catch (RuntimeException e) {
diff --git a/core/src/main/java/hudson/util/ProcessTree.java b/core/src/main/java/hudson/util/ProcessTree.java
index 8fbb80c8a8db..21e811a877fd 100644
--- a/core/src/main/java/hudson/util/ProcessTree.java
+++ b/core/src/main/java/hudson/util/ProcessTree.java
@@ -455,7 +455,7 @@ public static ProcessTree get() {
}
// Null-check in case the previous call worked
- boolean vetoes = vetoersExist == null ? true : vetoersExist;
+ boolean vetoes = vetoersExist == null || vetoersExist;
try {
if (File.pathSeparatorChar == ';')
@@ -2115,12 +2115,12 @@ public T act(ProcessCallable callable) throws IOException, InterruptedExc
}
/*
- On MacOS X, there's no procfs
- instead you'd do it with the sysctl
-
+ On MacOS X, there's no procfs
+ instead you'd do it with sysctl
+
There's CLI but that doesn't seem to offer the access to per-process info
-
+
diff --git a/core/src/main/java/hudson/util/RemotingDiagnostics.java b/core/src/main/java/hudson/util/RemotingDiagnostics.java
index 1756d2933b18..06a42beee69d 100644
--- a/core/src/main/java/hudson/util/RemotingDiagnostics.java
+++ b/core/src/main/java/hudson/util/RemotingDiagnostics.java
@@ -217,7 +217,7 @@ public void doHeapDump(StaplerRequest req, StaplerResponse rsp) throws IOExcepti
FilePath dump = obtain();
try {
- dump.copyTo(rsp.getCompressedOutputStream(req));
+ dump.copyTo(rsp.getOutputStream());
} finally {
dump.delete();
}
diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java
index cf1b5e9bcaab..774008e5e694 100644
--- a/core/src/main/java/hudson/util/XStream2.java
+++ b/core/src/main/java/hudson/util/XStream2.java
@@ -469,7 +469,7 @@ public Class realClass(String elementName) {
*/
private static final class AssociatedConverterImpl implements Converter {
private final XStream xstream;
- private static final ClassValue> classCache = new ClassValue>() {
+ private static final ClassValue> classCache = new ClassValue<>() {
@Override
protected Class extends ConverterMatcher> computeValue(Class> type) {
return computeConverterClass(type);
diff --git a/core/src/main/java/hudson/views/ListViewColumn.java b/core/src/main/java/hudson/views/ListViewColumn.java
index 1796f4cc495c..91e93e2145da 100644
--- a/core/src/main/java/hudson/views/ListViewColumn.java
+++ b/core/src/main/java/hudson/views/ListViewColumn.java
@@ -157,8 +157,7 @@ private static List createDefaultInitialColumnList(List d : descriptors)
try {
- if (d instanceof ListViewColumnDescriptor) {
- ListViewColumnDescriptor ld = (ListViewColumnDescriptor) d;
+ if (d instanceof ListViewColumnDescriptor ld) {
if (!ld.shownByDefault()) {
continue; // skip this
}
diff --git a/core/src/main/java/jenkins/agents/CloudSet.java b/core/src/main/java/jenkins/agents/CloudSet.java
index addefceb83ff..163556a001d1 100644
--- a/core/src/main/java/jenkins/agents/CloudSet.java
+++ b/core/src/main/java/jenkins/agents/CloudSet.java
@@ -41,7 +41,6 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
@@ -131,7 +130,7 @@ public String getCloudUpdateCenterCategoryLabel() {
@Override
public ModelObjectWithContextMenu.ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
ModelObjectWithContextMenu.ContextMenu m = new ModelObjectWithContextMenu.ContextMenu();
- Jenkins.get().clouds.stream().forEach(c -> m.add(c));
+ Jenkins.get().clouds.stream().forEach(m::add);
return m;
}
@@ -265,7 +264,7 @@ public void doReorder(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
}
var namesList = Arrays.asList(names);
var clouds = new ArrayList<>(Jenkins.get().clouds);
- Collections.sort(clouds, Comparator.comparingInt(c -> getIndexOf(namesList, c)));
+ clouds.sort(Comparator.comparingInt(c -> getIndexOf(namesList, c)));
Jenkins.get().clouds.replaceBy(clouds);
rsp.sendRedirect2(".");
}
diff --git a/core/src/main/java/jenkins/agents/WebSocketAgents.java b/core/src/main/java/jenkins/agents/WebSocketAgents.java
index 005587c6365a..d9560f156ebd 100644
--- a/core/src/main/java/jenkins/agents/WebSocketAgents.java
+++ b/core/src/main/java/jenkins/agents/WebSocketAgents.java
@@ -36,6 +36,7 @@
import hudson.remoting.ChannelBuilder;
import hudson.remoting.ChunkHeader;
import hudson.remoting.Engine;
+import hudson.slaves.SlaveComputer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
@@ -107,7 +108,9 @@ public HttpResponse doIndex(StaplerRequest req, StaplerResponse rsp) throws IOEx
Capability remoteCapability = Capability.fromASCII(remoteCapabilityStr);
LOGGER.fine(() -> "received " + remoteCapability);
rsp.setHeader(Capability.KEY, new Capability().toASCII());
- rsp.setHeader(Engine.REMOTING_MINIMUM_VERSION_HEADER, RemotingVersionInfo.getMinimumSupportedVersion().toString());
+ if (!SlaveComputer.ALLOW_UNSUPPORTED_REMOTING_VERSIONS) {
+ rsp.setHeader(Engine.REMOTING_MINIMUM_VERSION_HEADER, RemotingVersionInfo.getMinimumSupportedVersion().toString());
+ }
rsp.setHeader(Engine.WEBSOCKET_COOKIE_HEADER, cookie);
return WebSockets.upgrade(new Session(state, agent, remoteCapability));
}
diff --git a/core/src/main/java/jenkins/diagnostics/URICheckEncodingMonitor.java b/core/src/main/java/jenkins/diagnostics/URICheckEncodingMonitor.java
index 3563699b19b7..9fdd7bc38436 100644
--- a/core/src/main/java/jenkins/diagnostics/URICheckEncodingMonitor.java
+++ b/core/src/main/java/jenkins/diagnostics/URICheckEncodingMonitor.java
@@ -7,6 +7,7 @@
import hudson.model.AdministrativeMonitor;
import hudson.util.FormValidation;
import java.io.IOException;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -22,7 +23,7 @@ public class URICheckEncodingMonitor extends AdministrativeMonitor {
private static final Logger LOGGER = Logger.getLogger(URICheckEncodingMonitor.class.getName());
public boolean isCheckEnabled() {
- return !"ISO-8859-1".equalsIgnoreCase(System.getProperty("file.encoding"));
+ return !"ISO-8859-1".equalsIgnoreCase(Charset.defaultCharset().displayName());
}
@Override
diff --git a/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java b/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java
index d3154e6db6a3..409788ba5706 100644
--- a/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java
+++ b/core/src/main/java/jenkins/fingerprints/FileFingerprintStorage.java
@@ -101,11 +101,10 @@ public FileFingerprintStorage() {}
try {
Object loaded = configFile.read();
- if (!(loaded instanceof Fingerprint)) {
+ if (!(loaded instanceof Fingerprint f)) {
throw new IOException("Unexpected Fingerprint type. Expected " + Fingerprint.class + " or subclass but got "
+ (loaded != null ? loaded.getClass() : "null"));
}
- Fingerprint f = (Fingerprint) loaded;
if (f.getPersistedFacets() == null) {
logger.log(Level.WARNING, "Malformed fingerprint {0}: Missing facets", configFile);
Files.deleteIfExists(Util.fileToPath(file));
diff --git a/core/src/main/java/jenkins/install/InstallUtil.java b/core/src/main/java/jenkins/install/InstallUtil.java
index 3d1bf24e267d..6fb37e20bf3a 100644
--- a/core/src/main/java/jenkins/install/InstallUtil.java
+++ b/core/src/main/java/jenkins/install/InstallUtil.java
@@ -234,7 +234,7 @@ public static void saveLastExecVersion() {
if (configFile.exists()) {
try {
String lastVersion = XMLUtils.getValue("/hudson/version", configFile);
- if (lastVersion.length() > 0) {
+ if (!lastVersion.isEmpty()) {
LOGGER.log(Level.FINE, "discovered serialized lastVersion {0}", lastVersion);
return lastVersion;
}
diff --git a/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java b/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java
index cc1d5214e0ae..5b94090f4e3b 100644
--- a/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java
+++ b/core/src/main/java/jenkins/management/AdministrativeMonitorsDecorator.java
@@ -139,7 +139,7 @@ private Collection getAllActiveAdministrativeMonitors() {
* @return the list of active monitors if we should display them, otherwise null.
*/
public Collection getMonitorsToDisplay() {
- if (!Jenkins.get().hasPermission(Jenkins.SYSTEM_READ)) {
+ if (!(AdministrativeMonitor.hasPermissionToDisplay())) {
return null;
}
@@ -150,7 +150,7 @@ public Collection getMonitorsToDisplay() {
}
List ancestors = req.getAncestors();
- if (ancestors == null || ancestors.size() == 0) {
+ if (ancestors == null || ancestors.isEmpty()) {
// ???
return null;
}
diff --git a/core/src/main/java/jenkins/model/GlobalComputerRetentionCheckIntervalConfiguration.java b/core/src/main/java/jenkins/model/GlobalComputerRetentionCheckIntervalConfiguration.java
new file mode 100644
index 000000000000..46d760fe8ee2
--- /dev/null
+++ b/core/src/main/java/jenkins/model/GlobalComputerRetentionCheckIntervalConfiguration.java
@@ -0,0 +1,72 @@
+package jenkins.model;
+
+import hudson.Extension;
+import hudson.model.PersistentDescriptor;
+import java.util.logging.Logger;
+import net.sf.json.JSONException;
+import net.sf.json.JSONObject;
+import org.jenkinsci.Symbol;
+import org.kohsuke.stapler.StaplerRequest;
+
+/**
+ * Configures check interval for computer retention.
+ *
+ * @author Jakob Ackermann
+ */
+@Extension(ordinal = 401) @Symbol("computerRetentionCheckInterval")
+public class GlobalComputerRetentionCheckIntervalConfiguration extends GlobalConfiguration implements PersistentDescriptor {
+ /**
+ * For {@link hudson.slaves.ComputerRetentionWork#getRecurrencePeriod()}
+ */
+ private int computerRetentionCheckInterval = 60;
+
+ /**
+ * Gets the check interval for computer retention.
+ *
+ * @since 2.463
+ */
+ public int getComputerRetentionCheckInterval() {
+ if (computerRetentionCheckInterval <= 0) {
+ LOGGER.info("computerRetentionCheckInterval must be greater than zero, falling back to 60s");
+ return 60;
+ }
+ if (computerRetentionCheckInterval > 60) {
+ LOGGER.info("computerRetentionCheckInterval is limited to 60s");
+ return 60;
+ }
+ return computerRetentionCheckInterval;
+ }
+
+ /**
+ * Updates the check interval for computer retention and restarts the check cycle.
+ *
+ * @param interval new check interval in seconds
+ * @throws IllegalArgumentException interval must be greater than zero
+ * @since 2.463
+ */
+ private void setComputerRetentionCheckInterval(int interval) throws IllegalArgumentException {
+ if (interval <= 0) {
+ throw new IllegalArgumentException("interval must be greater than zero");
+ }
+ if (interval > 60) {
+ throw new IllegalArgumentException("interval must be below or equal 60s");
+ }
+ computerRetentionCheckInterval = interval;
+ save();
+ }
+
+ @Override
+ public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
+ try {
+ final int interval = json.getInt("computerRetentionCheckInterval");
+ setComputerRetentionCheckInterval(interval);
+ return true;
+ } catch (IllegalArgumentException e) {
+ throw new FormException(e, "computerRetentionCheckInterval");
+ } catch (JSONException e) {
+ throw new FormException(e.getMessage(), "computerRetentionCheckInterval");
+ }
+ }
+
+ private static final Logger LOGGER = Logger.getLogger(GlobalComputerRetentionCheckIntervalConfiguration.class.getName());
+}
diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java
index 7fe515fddf0e..a3376f99d75e 100644
--- a/core/src/main/java/jenkins/model/Jenkins.java
+++ b/core/src/main/java/jenkins/model/Jenkins.java
@@ -499,7 +499,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* STARTUP_MARKER_FILE.get(); // returns false if we are on a fresh startup. True for next startups.
* }
*/
- private static transient FileBoolean STARTUP_MARKER_FILE;
+ private static FileBoolean STARTUP_MARKER_FILE;
private volatile List jdks = new ArrayList<>();
@@ -2355,12 +2355,12 @@ public AdministrativeMonitor getAdministrativeMonitor(String id) {
* @since 2.64
*/
public List getActiveAdministrativeMonitors() {
- if (!Jenkins.get().hasPermission(SYSTEM_READ)) {
+ if (!AdministrativeMonitor.hasPermissionToDisplay()) {
return Collections.emptyList();
}
return administrativeMonitors.stream().filter(m -> {
try {
- return Jenkins.get().hasPermission(m.getRequiredPermission()) && m.isEnabled() && m.isActivated();
+ return m.hasRequiredPermission() && m.isEnabled() && m.isActivated();
} catch (Throwable x) {
LOGGER.log(Level.WARNING, null, x);
return false;
@@ -3106,8 +3106,7 @@ public Item getItem(String pathName, ItemGroup context) {
continue;
}
- if (ctx instanceof ItemGroup) {
- ItemGroup g = (ItemGroup) ctx;
+ if (ctx instanceof ItemGroup g) {
Item i = g.getItem(s);
if (i == null || !i.hasPermission(Item.READ)) { // TODO consider DISCOVER
ctx = null; // can't go up further
@@ -3477,10 +3476,9 @@ private void setBuildsAndWorkspacesDir() throws IOException, InvalidBuildsDir {
File d = new File(replacedValue);
if (!d.isDirectory()) {
// if dir does not exist (almost guaranteed) need to make sure nearest existing ancestor can be written to
- d = d.getParentFile();
- while (!d.exists()) {
+ do {
d = d.getParentFile();
- }
+ } while (!d.exists());
if (!d.canWrite()) {
throw new InvalidBuildsDir(newBuildsDirValue + " does not exist and probably cannot be created");
}
diff --git a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
index be71a97c7379..662ffa9359e2 100644
--- a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
+++ b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java
@@ -218,6 +218,7 @@ public final void doBuild(StaplerRequest req, StaplerResponse rsp, @QueryParamet
Queue.Item item = Jenkins.get().getQueue().schedule2(asJob(), delay.getTimeInSeconds(), getBuildCause(asJob(), req)).getItem();
if (item != null) {
+ // TODO JENKINS-66105 use SC_SEE_OTHER if !ScheduleResult.created
rsp.sendRedirect(SC_CREATED, req.getContextPath() + '/' + item.getUrl());
} else {
rsp.sendRedirect(".");
@@ -346,7 +347,7 @@ static ParameterizedJob resolveForCLI(@Argument(required = true, metaVar = "NAME
* (Would have been done entirely as an interface with default methods had this been designed for Java 8.)
*/
default ParameterizedJobMixIn getParameterizedJobMixIn() {
- return new ParameterizedJobMixIn() {
+ return new ParameterizedJobMixIn<>() {
@SuppressWarnings("unchecked") // untypable
@Override protected JobT asJob() {
return (JobT) ParameterizedJob.this;
diff --git a/test/src/test/resources/jenkins/security/stapler/StaplerDispatchValidatorTest/Groovy/compress.groovy b/core/src/main/java/jenkins/model/experimentalflags/RemoveYuiUserExperimentalFlag.java
similarity index 60%
rename from test/src/test/resources/jenkins/security/stapler/StaplerDispatchValidatorTest/Groovy/compress.groovy
rename to core/src/main/java/jenkins/model/experimentalflags/RemoveYuiUserExperimentalFlag.java
index 2c342e42bf97..e8f8dcc31775 100644
--- a/test/src/test/resources/jenkins/security/stapler/StaplerDispatchValidatorTest/Groovy/compress.groovy
+++ b/core/src/main/java/jenkins/model/experimentalflags/RemoveYuiUserExperimentalFlag.java
@@ -1,7 +1,7 @@
/*
* The MIT License
*
- * Copyright (c) 2019 CloudBees, Inc.
+ * Copyright (c) 2024, Markus Winter
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -22,13 +22,28 @@
* THE SOFTWARE.
*/
-package jenkins.security.stapler.StaplerDispatchValidatorTest.Groovy
+package jenkins.model.experimentalflags;
-def st = namespace('jelly:stapler')
-def l = namespace('/lib/layout')
-st.compress {
- l.view {
- st.contentType(value: 'text/html')
- st.include(page: 'frag')
+import edu.umd.cs.findbugs.annotations.Nullable;
+import hudson.Extension;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+@Extension
+@Restricted(NoExternalUse.class)
+public class RemoveYuiUserExperimentalFlag extends BooleanUserExperimentalFlag {
+ public RemoveYuiUserExperimentalFlag() {
+ super("remove-yui.flag");
+ }
+
+ @Override
+ public String getDisplayName() {
+ return "Remove YUI";
+ }
+
+ @Nullable
+ @Override
+ public String getShortDescription() {
+ return "Remove YUI from all Jenkins UI pages. This will break anything that depends on YUI";
}
}
diff --git a/core/src/main/java/jenkins/model/experimentalflags/UserExperimentalFlagsProperty.java b/core/src/main/java/jenkins/model/experimentalflags/UserExperimentalFlagsProperty.java
index 6732f2e5d696..85332d26973c 100644
--- a/core/src/main/java/jenkins/model/experimentalflags/UserExperimentalFlagsProperty.java
+++ b/core/src/main/java/jenkins/model/experimentalflags/UserExperimentalFlagsProperty.java
@@ -31,6 +31,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import java.util.HashMap;
import java.util.Map;
import net.sf.json.JSONObject;
@@ -76,13 +77,19 @@ public static final class DescriptorImpl extends UserPropertyDescriptor {
public UserProperty newInstance(@Nullable StaplerRequest req, @NonNull JSONObject formData) throws FormException {
JSONObject flagsObj = formData.getJSONObject("flags");
Map flags = new HashMap<>();
- for (Object key : flagsObj.keySet()) {
- String value = (String) flagsObj.get((String) key);
+ for (String key : flagsObj.keySet()) {
+ String value = (String) flagsObj.get(key);
if (!value.isEmpty()) {
- flags.put((String) key, value);
+ flags.put(key, value);
}
}
return new UserExperimentalFlagsProperty(flags);
}
+
+ @NonNull
+ @Override
+ public UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Experimental.class);
+ }
}
}
diff --git a/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java b/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java
index 203ea976bccd..c73c5cbf80f1 100644
--- a/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java
+++ b/core/src/main/java/jenkins/model/lazy/LazyBuildMixIn.java
@@ -298,7 +298,7 @@ public List getEstimatedDurationCandidates() {
/**
* @deprecated Remove any code calling this method, history widget is now created via {@link jenkins.widgets.WidgetFactory} implementation.
*/
- @Deprecated(forRemoval = true, since = "TODO")
+ @Deprecated(forRemoval = true, since = "2.459")
public final HistoryWidget createHistoryWidget() {
throw new IllegalStateException("HistoryWidget is now created via WidgetFactory implementation");
}
diff --git a/core/src/main/java/jenkins/model/queue/ItemDeletion.java b/core/src/main/java/jenkins/model/queue/ItemDeletion.java
index e5c9fc2ca87e..fd18edde04a5 100644
--- a/core/src/main/java/jenkins/model/queue/ItemDeletion.java
+++ b/core/src/main/java/jenkins/model/queue/ItemDeletion.java
@@ -28,25 +28,42 @@
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.ExtensionList;
+import hudson.model.AbstractItem;
import hudson.model.Action;
+import hudson.model.Computer;
+import hudson.model.Executor;
+import hudson.model.Failure;
import hudson.model.Item;
+import hudson.model.Messages;
import hudson.model.Queue;
+import hudson.model.Result;
+import hudson.model.queue.Executables;
+import hudson.model.queue.SubTask;
import hudson.model.queue.Tasks;
+import hudson.model.queue.WorkUnit;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Logger;
+import jenkins.model.Jenkins;
import net.jcip.annotations.GuardedBy;
/**
* A {@link Queue.QueueDecisionHandler} that blocks items being deleted from entering the queue.
- *
+ * @see AbstractItem#delete()
* @since 2.55
*/
@Extension
public class ItemDeletion extends Queue.QueueDecisionHandler {
+ private static final Logger LOGGER = Logger.getLogger(ItemDeletion.class.getName());
+
/**
* Lock to guard the {@link #registrations} set.
*/
@@ -176,4 +193,94 @@ public boolean shouldSchedule(Queue.Task p, List actions) {
}
return true;
}
+
+ /**
+ * Cancels any builds in progress of this item (if a job) or descendants (if a folder).
+ * Also cancels any associated queue items.
+ * @param initiatingItem an item being deleted
+ * @since TODO
+ */
+ public static void cancelBuildsInProgress(@NonNull Item initiatingItem) throws Failure, InterruptedException {
+ Queue queue = Queue.getInstance();
+ if (initiatingItem instanceof Queue.Task) {
+ // clear any items in the queue so they do not get picked up
+ queue.cancel((Queue.Task) initiatingItem);
+ }
+ // now cancel any child items - this happens after ItemDeletion registration, so we can use a snapshot
+ for (Queue.Item i : queue.getItems()) {
+ Item item = Tasks.getItemOf(i.task);
+ while (item != null) {
+ if (item == initiatingItem) {
+ if (!queue.cancel(i)) {
+ LOGGER.warning(() -> "failed to cancel " + i);
+ }
+ break;
+ }
+ if (item.getParent() instanceof Item) {
+ item = (Item) item.getParent();
+ } else {
+ break;
+ }
+ }
+ }
+ // interrupt any builds in progress (and this should be a recursive test so that folders do not pay
+ // the 15 second delay for every child item). This happens after queue cancellation, so will be
+ // a complete set of builds in flight
+ Map buildsInProgress = new LinkedHashMap<>();
+ for (Computer c : Jenkins.get().getComputers()) {
+ for (Executor e : c.getAllExecutors()) {
+ final WorkUnit workUnit = e.getCurrentWorkUnit();
+ final Queue.Executable executable = workUnit != null ? workUnit.getExecutable() : null;
+ final SubTask subtask = executable != null ? Executables.getParentOf(executable) : null;
+ if (subtask != null) {
+ Item item = Tasks.getItemOf(subtask);
+ while (item != null) {
+ if (item == initiatingItem) {
+ buildsInProgress.put(e, e.getCurrentExecutable());
+ e.interrupt(Result.ABORTED);
+ break;
+ }
+ if (item.getParent() instanceof Item) {
+ item = (Item) item.getParent();
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!buildsInProgress.isEmpty()) {
+ // give them 15 seconds or so to respond to the interrupt
+ long expiration = System.nanoTime() + TimeUnit.SECONDS.toNanos(15);
+ // comparison with executor.getCurrentExecutable() == computation currently should always be true
+ // as we no longer recycle Executors, but safer to future-proof in case we ever revisit recycling
+ while (!buildsInProgress.isEmpty() && expiration - System.nanoTime() > 0L) {
+ // we know that ItemDeletion will prevent any new builds in the queue
+ // ItemDeletion happens-before Queue.cancel so we know that the Queue will stay clear
+ // Queue.cancel happens-before collecting the buildsInProgress list
+ // thus buildsInProgress contains the complete set we need to interrupt and wait for
+ for (Iterator> iterator =
+ buildsInProgress.entrySet().iterator();
+ iterator.hasNext(); ) {
+ Map.Entry entry = iterator.next();
+ // comparison with executor.getCurrentExecutable() == executable currently should always be
+ // true as we no longer recycle Executors, but safer to future-proof in case we ever
+ // revisit recycling.
+ if (!entry.getKey().isAlive()
+ || entry.getValue() != entry.getKey().getCurrentExecutable()) {
+ iterator.remove();
+ }
+ // I don't know why, but we have to keep interrupting
+ entry.getKey().interrupt(Result.ABORTED);
+ }
+ Thread.sleep(50L);
+ }
+ if (!buildsInProgress.isEmpty()) {
+ throw new Failure(Messages.AbstractItem_FailureToStopBuilds(
+ buildsInProgress.size(), initiatingItem.getFullDisplayName()
+ ));
+ }
+ }
+ }
+
}
diff --git a/core/src/main/java/jenkins/monitor/JavaVersionRecommendationAdminMonitor.java b/core/src/main/java/jenkins/monitor/JavaVersionRecommendationAdminMonitor.java
index 4b3642bb059c..c95740613765 100644
--- a/core/src/main/java/jenkins/monitor/JavaVersionRecommendationAdminMonitor.java
+++ b/core/src/main/java/jenkins/monitor/JavaVersionRecommendationAdminMonitor.java
@@ -78,16 +78,6 @@ public class JavaVersionRecommendationAdminMonitor extends AdministrativeMonitor
static {
NavigableMap supportedVersions = new TreeMap<>();
- // Adjust Java 11 end of life date for weekly and LTS
- if (Jenkins.VERSION.split("[.]").length > 2) {
- // LTS will require Java 17 or newer beginning 30 Oct 2024
- // https://groups.google.com/g/jenkinsci-dev/c/gsXAqOQQEPc/m/VT9IBYdmAQAJ
- supportedVersions.put(11, LocalDate.of(2024, 10, 30)); // Temurin: 2024-10-31
- } else {
- // Weekly will require Java 17 or newer beginning 18 Jun 2024
- // https://groups.google.com/g/jenkinsci-dev/c/gsXAqOQQEPc/m/4fn4Un1iAwAJ
- supportedVersions.put(11, LocalDate.of(2024, 6, 18)); // Temurin: 2024-10-31
- }
supportedVersions.put(17, LocalDate.of(2026, 3, 31)); // Temurin: 2027-10-31
supportedVersions.put(21, LocalDate.of(2027, 9, 30)); // Temurin: 2029-09-30
SUPPORTED_JAVA_VERSIONS = Collections.unmodifiableNavigableMap(supportedVersions);
diff --git a/core/src/main/java/jenkins/org/apache/commons/validator/routines/UrlValidator.java b/core/src/main/java/jenkins/org/apache/commons/validator/routines/UrlValidator.java
index 40c0eae1d13f..555d0c58f5e3 100644
--- a/core/src/main/java/jenkins/org/apache/commons/validator/routines/UrlValidator.java
+++ b/core/src/main/java/jenkins/org/apache/commons/validator/routines/UrlValidator.java
@@ -420,7 +420,7 @@ protected boolean isValidAuthority(String authority) {
}
}
String port = authorityMatcher.group(PARSE_AUTHORITY_PORT);
- if (port != null && port.length() > 0) {
+ if (port != null && !port.isEmpty()) {
try {
int iPort = Integer.parseInt(port);
if (iPort < 0 || iPort > MAX_UNSIGNED_16_BIT_INT) {
@@ -433,7 +433,7 @@ protected boolean isValidAuthority(String authority) {
}
String extra = authorityMatcher.group(PARSE_AUTHORITY_EXTRA);
- if (extra != null && extra.trim().length() > 0) {
+ if (extra != null && !extra.trim().isEmpty()) {
return false;
}
diff --git a/core/src/main/java/jenkins/security/ApiTokenProperty.java b/core/src/main/java/jenkins/security/ApiTokenProperty.java
index 69904a0e4785..464fdcbdf16c 100644
--- a/core/src/main/java/jenkins/security/ApiTokenProperty.java
+++ b/core/src/main/java/jenkins/security/ApiTokenProperty.java
@@ -33,6 +33,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.security.ACL;
import hudson.util.HttpResponses;
import hudson.util.Secret;
@@ -657,6 +658,11 @@ public HttpResponse doRevokeAllExcept(@AncestorInPath User u,
return HttpResponses.ok();
}
+
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Security.class);
+ }
}
/**
diff --git a/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java b/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java
index 52f74d452ffa..cb8b8accf295 100644
--- a/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java
+++ b/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java
@@ -6,6 +6,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.security.SecurityRealm;
import java.io.IOException;
import java.util.ArrayList;
@@ -171,6 +172,11 @@ public boolean isEnabled() {
public UserProperty newInstance(User user) {
return null;
}
+
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Invisible.class);
+ }
}
private static final Logger LOGGER = Logger.getLogger(LastGrantedAuthoritiesProperty.class.getName());
diff --git a/core/src/main/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizer.java b/core/src/main/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizer.java
index 733a6c89a1f2..1e201bcd3dbc 100644
--- a/core/src/main/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizer.java
+++ b/core/src/main/java/jenkins/security/RedactSecretJsonInErrorMessageSanitizer.java
@@ -88,7 +88,6 @@ private Object copyAndSanitize(Object value) {
}
}
- @SuppressWarnings("unchecked")
private JSONObject copyAndSanitizeObject(JSONObject jsonObject) {
Set redactedKeySet = retrieveRedactedKeys(jsonObject);
JSONObject result = new JSONObject();
diff --git a/core/src/main/java/jenkins/security/ResourceDomainConfiguration.java b/core/src/main/java/jenkins/security/ResourceDomainConfiguration.java
index 5c569ce40d50..cd552fc65130 100644
--- a/core/src/main/java/jenkins/security/ResourceDomainConfiguration.java
+++ b/core/src/main/java/jenkins/security/ResourceDomainConfiguration.java
@@ -150,8 +150,7 @@ private FormValidation checkUrl(String resourceRootUrlString, boolean allowOnlin
// Send a request to /instance-identity/ at the resource root URL and check whether it is this Jenkins
try {
URLConnection urlConnection = new URI(resourceRootUrlString + "instance-identity/").toURL().openConnection();
- if (urlConnection instanceof HttpURLConnection) {
- HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
+ if (urlConnection instanceof HttpURLConnection httpURLConnection) {
int responseCode = httpURLConnection.getResponseCode();
if (responseCode == 200) {
diff --git a/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java b/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java
index 02753addb79d..e87506315a41 100644
--- a/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java
+++ b/core/src/main/java/jenkins/security/seed/UserSeedChangeListener.java
@@ -35,7 +35,7 @@
/**
* Listener notified when a user was requested to changed their seed
- * @since 2.160 and 2.150.2, but restricted (unavailable to plugins) before TODO
+ * @since 2.160 and 2.150.2, but restricted (unavailable to plugins) before 2.406
*/
public abstract class UserSeedChangeListener implements ExtensionPoint {
private static final Logger LOGGER = Logger.getLogger(SecurityListener.class.getName());
diff --git a/core/src/main/java/jenkins/security/seed/UserSeedProperty.java b/core/src/main/java/jenkins/security/seed/UserSeedProperty.java
index d7420910cb64..968ee9320f58 100644
--- a/core/src/main/java/jenkins/security/seed/UserSeedProperty.java
+++ b/core/src/main/java/jenkins/security/seed/UserSeedProperty.java
@@ -31,6 +31,7 @@
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
+import hudson.model.userproperty.UserPropertyCategory;
import hudson.util.HttpResponses;
import java.io.IOException;
import java.security.SecureRandom;
@@ -153,5 +154,10 @@ public synchronized HttpResponse doRenewSessionSeed(@AncestorInPath @NonNull Use
public boolean isEnabled() {
return !DISABLE_USER_SEED && !HIDE_USER_SEED_SECTION;
}
+
+ @Override
+ public @NonNull UserPropertyCategory getUserPropertyCategory() {
+ return UserPropertyCategory.get(UserPropertyCategory.Security.class);
+ }
}
}
diff --git a/core/src/main/java/jenkins/util/TreeString.java b/core/src/main/java/jenkins/util/TreeString.java
index 3bcdc95d90ca..58890d225893 100644
--- a/core/src/main/java/jenkins/util/TreeString.java
+++ b/core/src/main/java/jenkins/util/TreeString.java
@@ -64,7 +64,7 @@ public final class TreeString implements Serializable {
}
/* package */TreeString(final TreeString parent, final String label) {
- assert parent == null || label.length() > 0; // if there's a parent,
+ assert parent == null || !label.isEmpty(); // if there's a parent,
// label can't be empty.
this.parent = parent;
diff --git a/core/src/main/java/jenkins/util/URLClassLoader2.java b/core/src/main/java/jenkins/util/URLClassLoader2.java
index 3fdedcbeada0..a6e5194e14f3 100644
--- a/core/src/main/java/jenkins/util/URLClassLoader2.java
+++ b/core/src/main/java/jenkins/util/URLClassLoader2.java
@@ -20,7 +20,7 @@ public class URLClassLoader2 extends URLClassLoader implements JenkinsClassLoade
/**
* @deprecated use {@link URLClassLoader2#URLClassLoader2(String, URL[])}
*/
- @Deprecated(since = "TODO")
+ @Deprecated(since = "2.459")
public URLClassLoader2(URL[] urls) {
super(urls);
}
@@ -28,7 +28,7 @@ public URLClassLoader2(URL[] urls) {
/**
* @deprecated use {@link URLClassLoader2#URLClassLoader2(String, URL[], ClassLoader)}
*/
- @Deprecated(since = "TODO")
+ @Deprecated(since = "2.459")
public URLClassLoader2(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
@@ -37,7 +37,7 @@ public URLClassLoader2(URL[] urls, ClassLoader 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
+ * @since 2.459
*/
public URLClassLoader2(String name, URL[] urls) {
super(name, urls, getSystemClassLoader());
@@ -48,7 +48,7 @@ public URLClassLoader2(String name, URL[] urls) {
* @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
+ * @since 2.459
*/
public URLClassLoader2(String name, URL[] urls, ClassLoader parent) {
super(name, urls, parent);
diff --git a/core/src/main/java/jenkins/util/VirtualFile.java b/core/src/main/java/jenkins/util/VirtualFile.java
index bf9fc49bd48d..6c754a50bbc4 100644
--- a/core/src/main/java/jenkins/util/VirtualFile.java
+++ b/core/src/main/java/jenkins/util/VirtualFile.java
@@ -46,6 +46,7 @@
import java.io.Serializable;
import java.net.URI;
import java.net.URL;
+import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.OpenOption;
@@ -375,7 +376,7 @@ public int zip(OutputStream outputStream, String includes, String excludes, bool
Collection files = list(includes, excludes, useDefaultExcludes, openOptions);
try (ZipOutputStream zos = new ZipOutputStream(outputStream)) {
- zos.setEncoding(System.getProperty("file.encoding")); // TODO JENKINS-20663 make this overridable via query parameter
+ zos.setEncoding(Charset.defaultCharset().displayName()); // TODO JENKINS-20663 make this overridable via query parameter
for (String relativePath : files) {
VirtualFile virtualFile = this.child(relativePath);
diff --git a/core/src/main/java/jenkins/widgets/BuildTimeTrend.java b/core/src/main/java/jenkins/widgets/BuildTimeTrend.java
index 59ab879f05da..dd0f0434ecb3 100644
--- a/core/src/main/java/jenkins/widgets/BuildTimeTrend.java
+++ b/core/src/main/java/jenkins/widgets/BuildTimeTrend.java
@@ -25,7 +25,9 @@
package jenkins.widgets;
import hudson.model.AbstractBuild;
+import hudson.model.AbstractProject;
import hudson.model.BallColor;
+import hudson.model.Job;
import hudson.model.Node;
import hudson.model.Run;
import jenkins.console.ConsoleUrlProvider;
@@ -37,6 +39,10 @@
@Restricted(DoNotUse.class) // only for buildTimeTrend.jelly
public class BuildTimeTrend extends RunListProgressiveRendering {
+ public boolean isAbstractProject(Job, ?> job) {
+ return job instanceof AbstractProject;
+ }
+
@Override protected void calculate(Run, ?> build, JSONObject element) {
BallColor iconColor = build.getIconColor();
element.put("iconName", iconColor.getIconName());
@@ -46,6 +52,8 @@ public class BuildTimeTrend extends RunListProgressiveRendering {
element.put("displayName", build.getDisplayName());
element.put("duration", build.getDuration());
element.put("durationString", build.getDurationString());
+ element.put("timestampString", build.getTimestampString());
+ element.put("timestampString2", build.getTimestampString2());
element.put("consoleUrl", ConsoleUrlProvider.getRedirectUrl(build));
if (build instanceof AbstractBuild) {
AbstractBuild, ?> b = (AbstractBuild) build;
diff --git a/core/src/main/java/jenkins/widgets/HistoryPageEntry.java b/core/src/main/java/jenkins/widgets/HistoryPageEntry.java
index ac5e0a772692..98f0a7aac58e 100644
--- a/core/src/main/java/jenkins/widgets/HistoryPageEntry.java
+++ b/core/src/main/java/jenkins/widgets/HistoryPageEntry.java
@@ -57,8 +57,7 @@ public long getEntryId() {
protected static long getEntryId(@NonNull Object entry) {
if (entry instanceof QueueItem) {
return ((QueueItem) entry).getId();
- } else if (entry instanceof Run) {
- Run run = (Run) entry;
+ } else if (entry instanceof Run run) {
return Long.MIN_VALUE + run.getNumber();
} else if (entry instanceof Number) {
// Used for testing purposes because of JENKINS-30899 and JENKINS-30909
diff --git a/core/src/main/java/org/jenkins/ui/icon/Icon.java b/core/src/main/java/org/jenkins/ui/icon/Icon.java
index e99822dac653..95524c8c4b74 100644
--- a/core/src/main/java/org/jenkins/ui/icon/Icon.java
+++ b/core/src/main/java/org/jenkins/ui/icon/Icon.java
@@ -279,7 +279,7 @@ public static String toNormalizedCSSSelector(String classNames) {
// Trim all tokens first
for (String classNameTok : classNameTokA) {
String trimmedToken = classNameTok.trim();
- if (trimmedToken.length() > 0) {
+ if (!trimmedToken.isEmpty()) {
classNameTokL.add(trimmedToken);
}
}
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 9a07556d7c71..52c7faee0249 100644
--- a/core/src/main/java/org/jenkins/ui/symbol/Symbol.java
+++ b/core/src/main/java/org/jenkins/ui/symbol/Symbol.java
@@ -14,6 +14,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.regex.Matcher;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -72,13 +73,13 @@ public static String get(@NonNull SymbolRequest request) {
.computeIfAbsent(identifier, key -> new ConcurrentHashMap<>())
.computeIfAbsent(name, key -> loadSymbol(identifier, key));
if ((tooltip != null && !tooltip.isBlank()) && (htmlTooltip == null || htmlTooltip.isBlank())) {
- symbol = symbol.replaceAll("