mirror of https://github.com/jenkinsci/jenkins.git
Merge branch 'master' into rename-configuration-to-configure
This commit is contained in:
commit
3ef51444c5
|
@ -54,7 +54,7 @@ jobs:
|
|||
repositories: >-
|
||||
["jenkins.io"]
|
||||
- name: Check out
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Publish jenkins.io changelog draft
|
||||
|
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
steps:
|
||||
- name: Check if PR targets LTS branch
|
||||
if: startsWith(github.event.pull_request.base.ref, 'stable-')
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
|
|
@ -15,9 +15,9 @@ jobs:
|
|||
is-lts: ${{ steps.set-version.outputs.is-lts }}
|
||||
is-rc: ${{ steps.set-version.outputs.is-rc }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 17
|
||||
|
@ -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@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
||||
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@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
||||
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@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
||||
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@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
||||
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@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2
|
||||
uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'jenkinsci' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run update-since-todo.py
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
<extension>
|
||||
<groupId>io.jenkins.tools.incrementals</groupId>
|
||||
<artifactId>git-changelist-maven-extension</artifactId>
|
||||
<version>1.10</version>
|
||||
<version>1.13</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
|
|
2
ath.sh
2
ath.sh
|
@ -6,7 +6,7 @@ set -o xtrace
|
|||
cd "$(dirname "$0")"
|
||||
|
||||
# https://github.com/jenkinsci/acceptance-test-harness/releases
|
||||
export ATH_VERSION=6300.v12732144c83f
|
||||
export ATH_VERSION=6361.vcb_036a_7ffb_a_5
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
export JDK=17
|
||||
|
|
12
bom/pom.xml
12
bom/pom.xml
|
@ -40,8 +40,8 @@ THE SOFTWARE.
|
|||
<properties>
|
||||
<commons-fileupload2.version>2.0.0-M4</commons-fileupload2.version>
|
||||
<groovy.version>2.4.21</groovy.version>
|
||||
<jelly.version>1.1-jenkins-20250616</jelly.version>
|
||||
<stapler.version>1997.v356365fb_929e</stapler.version>
|
||||
<jelly.version>1.1-jenkins-20250731</jelly.version>
|
||||
<stapler.version>2030.v88a_855365981</stapler.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
@ -63,7 +63,7 @@ THE SOFTWARE.
|
|||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-framework-bom</artifactId>
|
||||
<version>6.2.9</version>
|
||||
<version>6.2.10</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
@ -71,7 +71,7 @@ THE SOFTWARE.
|
|||
<!-- https://docs.spring.io/spring-security/reference/6.3/getting-spring-security.html#getting-maven-no-boot -->
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-bom</artifactId>
|
||||
<version>6.5.2</version>
|
||||
<version>6.5.3</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
@ -240,7 +240,7 @@ THE SOFTWARE.
|
|||
<dependency>
|
||||
<groupId>org.jenkins-ci</groupId>
|
||||
<artifactId>crypto-util</artifactId>
|
||||
<version>1.10</version>
|
||||
<version>1.11</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jenkins-ci</groupId>
|
||||
|
@ -305,7 +305,7 @@ THE SOFTWARE.
|
|||
<dependency>
|
||||
<groupId>org.kohsuke.stapler</groupId>
|
||||
<artifactId>json-lib</artifactId>
|
||||
<version>2.4-jenkins-8</version>
|
||||
<version>2.4-jenkins-15</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.kohsuke.stapler</groupId>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<url>https://github.com/jenkinsci/jenkins</url>
|
||||
|
||||
<properties>
|
||||
<mina-sshd.version>2.15.0</mina-sshd.version>
|
||||
<mina-sshd.version>2.16.0</mina-sshd.version>
|
||||
<!-- Filled in by jacoco-maven-plugin -->
|
||||
<jacocoSurefireArgs />
|
||||
</properties>
|
||||
|
@ -38,11 +38,6 @@
|
|||
<artifactId>commons-io</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.sshd</groupId>
|
||||
<artifactId>sshd-common</artifactId>
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
|
||||
package hudson.util;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
@ -139,6 +140,9 @@ public class QuotedStringTokenizer
|
|||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
@SuppressFBWarnings(
|
||||
value = {"SF_DEAD_STORE_DUE_TO_SWITCH_FALLTHROUGH", "SF_SWITCH_FALLTHROUGH"},
|
||||
justification = "TODO needs triage")
|
||||
public boolean hasMoreTokens()
|
||||
{
|
||||
// Already found a token
|
||||
|
|
17
core/pom.xml
17
core/pom.xml
|
@ -117,11 +117,6 @@ THE SOFTWARE.
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.infradna.tool</groupId>
|
||||
<artifactId>bridge-method-annotation</artifactId>
|
||||
<version>${bridge-method-injector.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.xml.txw2</groupId>
|
||||
<artifactId>txw2</artifactId>
|
||||
|
@ -168,6 +163,11 @@ THE SOFTWARE.
|
|||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jenkins.tools</groupId>
|
||||
<artifactId>bridge-method-annotation</artifactId>
|
||||
<version>${bridge-method-injector.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- needed by Jelly -->
|
||||
<groupId>jakarta.servlet.jsp.jstl</groupId>
|
||||
|
@ -446,11 +446,6 @@ THE SOFTWARE.
|
|||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
|
@ -568,7 +563,7 @@ THE SOFTWARE.
|
|||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.infradna.tool</groupId>
|
||||
<groupId>io.jenkins.tools</groupId>
|
||||
<artifactId>bridge-method-injector</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
|
|
|
@ -33,6 +33,7 @@ import hudson.PluginWrapper.Dependency;
|
|||
import hudson.model.Hudson;
|
||||
import hudson.util.CyclicGraphDetector;
|
||||
import hudson.util.CyclicGraphDetector.CycleDetectedException;
|
||||
import hudson.util.DelegatingClassLoader;
|
||||
import hudson.util.IOUtils;
|
||||
import hudson.util.MaskingClassLoader;
|
||||
import java.io.File;
|
||||
|
@ -559,7 +560,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
|
|||
/**
|
||||
* Used to load classes from dependency plugins.
|
||||
*/
|
||||
static final class DependencyClassLoader extends ClassLoader {
|
||||
static final class DependencyClassLoader extends DelegatingClassLoader {
|
||||
/**
|
||||
* This classloader is created for this plugin. Useful during debugging.
|
||||
*/
|
||||
|
@ -574,10 +575,6 @@ public class ClassicPluginStrategy implements PluginStrategy {
|
|||
*/
|
||||
private volatile List<PluginWrapper> transitiveDependencies;
|
||||
|
||||
static {
|
||||
registerAsParallelCapable();
|
||||
}
|
||||
|
||||
DependencyClassLoader(ClassLoader parent, File archive, List<Dependency> dependencies, PluginManager pluginManager) {
|
||||
super("dependency ClassLoader for " + archive.getPath(), parent);
|
||||
this._for = archive;
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package hudson;
|
||||
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
||||
/**
|
||||
* @deprecated No longer does anything. Only here to prevent errors from old versions of tools like {@code JenkinsRule}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Restricted(NoExternalUse.class)
|
||||
public class DNSMultiCast {
|
||||
|
||||
public static boolean disabled = true;
|
||||
|
||||
}
|
|
@ -58,8 +58,10 @@ import hudson.security.ACL;
|
|||
import hudson.security.ACLContext;
|
||||
import hudson.security.Permission;
|
||||
import hudson.security.PermissionScope;
|
||||
import hudson.util.CachingClassLoader;
|
||||
import hudson.util.CyclicGraphDetector;
|
||||
import hudson.util.CyclicGraphDetector.CycleDetectedException;
|
||||
import hudson.util.ExistenceCheckingClassLoader;
|
||||
import hudson.util.FormValidation;
|
||||
import hudson.util.PersistedList;
|
||||
import hudson.util.Retrier;
|
||||
|
@ -106,13 +108,11 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -218,6 +218,14 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
|
|||
*/
|
||||
/* private final */ static int CHECK_UPDATE_ATTEMPTS;
|
||||
|
||||
/**
|
||||
* Class name prefixes to skip in the class loading
|
||||
*/
|
||||
private static final String[] CLASS_PREFIXES_TO_SKIP = {
|
||||
"SimpleTemplateScript", // cf. groovy.text.SimpleTemplateEngine
|
||||
"groovy.tmp.templates.GStringTemplateScript", // Leaks on classLoader in some cases, see JENKINS-75879
|
||||
};
|
||||
|
||||
static {
|
||||
try {
|
||||
// Secure initialization
|
||||
|
@ -2392,43 +2400,50 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
|
|||
/**
|
||||
* {@link ClassLoader} that can see all plugins.
|
||||
*/
|
||||
public static final class UberClassLoader extends ClassLoader {
|
||||
public static final class UberClassLoader extends CachingClassLoader {
|
||||
private final List<PluginWrapper> activePlugins;
|
||||
|
||||
/** Cache of loaded, or known to be unloadable, classes. */
|
||||
private final ConcurrentMap<String, Optional<Class<?>>> loaded = new ConcurrentHashMap<>();
|
||||
|
||||
static {
|
||||
registerAsParallelCapable();
|
||||
}
|
||||
|
||||
/**
|
||||
* The servlet container's {@link ClassLoader} (the parent of Jenkins core) is
|
||||
* parallel-capable and maintains its own growing {@link Map} of {@link
|
||||
* ClassLoader#getClassLoadingLock} objects per class name for every load attempt (including
|
||||
* misses), and we cannot override this behavior. Wrap the servlet container {@link
|
||||
* ClassLoader} in {@link ExistenceCheckingClassLoader} to avoid calling {@link
|
||||
* ClassLoader#getParent}'s {@link ClassLoader#loadClass(String, boolean)} at all for misses
|
||||
* by first checking if the resource exists. If the resource does not exist, we immediately
|
||||
* throw {@link ClassNotFoundException}. As a result, the servlet container's {@link
|
||||
* ClassLoader} is never asked to try and fail, and it never creates/retains lock objects
|
||||
* for those misses.
|
||||
*/
|
||||
public UberClassLoader(List<PluginWrapper> activePlugins) {
|
||||
super("UberClassLoader", PluginManager.class.getClassLoader());
|
||||
super("UberClassLoader", new ExistenceCheckingClassLoader(PluginManager.class.getClassLoader()));
|
||||
this.activePlugins = activePlugins;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
if (name.startsWith("SimpleTemplateScript")) { // cf. groovy.text.SimpleTemplateEngine
|
||||
throw new ClassNotFoundException("ignoring " + name);
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
for (String namePrefixToSkip : CLASS_PREFIXES_TO_SKIP) {
|
||||
if (name.startsWith(namePrefixToSkip)) {
|
||||
throw new ClassNotFoundException("ignoring " + name);
|
||||
}
|
||||
}
|
||||
return loaded.computeIfAbsent(name, this::computeValue).orElseThrow(() -> new ClassNotFoundException(name));
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
|
||||
private Optional<Class<?>> computeValue(String name) {
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
for (PluginWrapper p : activePlugins) {
|
||||
try {
|
||||
if (FAST_LOOKUP) {
|
||||
return Optional.of(ClassLoaderReflectionToolkit.loadClass(p.classLoader, name));
|
||||
return ClassLoaderReflectionToolkit.loadClass(p.classLoader, name);
|
||||
} else {
|
||||
return Optional.of(p.classLoader.loadClass(name));
|
||||
return p.classLoader.loadClass(name);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Not found. Try the next class loader.
|
||||
}
|
||||
}
|
||||
// Not found in any of the class loaders. Delegate.
|
||||
return Optional.empty();
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2460,10 +2475,6 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
|
|||
return Collections.enumeration(resources);
|
||||
}
|
||||
|
||||
void clearCacheMisses() {
|
||||
loaded.values().removeIf(Optional::isEmpty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// only for debugging purpose
|
||||
|
|
|
@ -497,7 +497,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable {
|
|||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
protected @NonNull Charset getClientCharset() throws IOException, InterruptedException {
|
||||
public @NonNull Charset getClientCharset() throws IOException, InterruptedException {
|
||||
if (encoding != null) {
|
||||
return encoding;
|
||||
}
|
||||
|
|
|
@ -66,6 +66,11 @@ public class ReverseProxySetupMonitor extends AdministrativeMonitor {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActivationFake() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Restricted(DoNotUse.class) // WebOnly
|
||||
@RestrictedSince("2.235")
|
||||
public HttpResponse doTest(StaplerRequest2 request, @QueryParameter boolean testWithContext) {
|
||||
|
|
|
@ -129,6 +129,7 @@ public abstract class AbstractItem extends Actionable implements Loadable, Item,
|
|||
doSetName(name);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@Exported(visibility = 999)
|
||||
public String getName() {
|
||||
|
@ -470,6 +471,7 @@ public abstract class AbstractItem extends Actionable implements Loadable, Item,
|
|||
@Override
|
||||
public abstract Collection<? extends Job> getAllJobs();
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@Exported
|
||||
public final String getFullName() {
|
||||
|
|
|
@ -160,6 +160,11 @@ public abstract class AdministrativeMonitor extends AbstractModelObject implemen
|
|||
*/
|
||||
public abstract boolean isActivated();
|
||||
|
||||
@Restricted(NoExternalUse.class)
|
||||
public boolean isActivationFake() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this monitor is security related.
|
||||
*
|
||||
|
@ -186,8 +191,7 @@ public abstract class AdministrativeMonitor extends AbstractModelObject implemen
|
|||
* By default {@link Jenkins#ADMINISTER}, but {@link Jenkins#SYSTEM_READ} or {@link Jenkins#MANAGE} are also supported.
|
||||
* <p>
|
||||
* 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
|
||||
* {@link jenkins.management.AdministrativeMonitorsDecorator} to users without Administer permission.
|
||||
* administrative monitor appear on {@link ManageJenkinsAction} to users without Administer permission.
|
||||
* {@link #doDisable(StaplerRequest2, StaplerResponse2)} will still always require Administer permission.
|
||||
* </p>
|
||||
* <p>
|
||||
|
|
|
@ -625,6 +625,19 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
|
|||
return !isOffline();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Uses {@link #getChannel()} to check the connection.
|
||||
* A connected agent may still be offline for scheduling if marked temporarily offline.
|
||||
* @return {@code true} if the agent is connected, {@code false} otherwise.
|
||||
* @see #isOffline()
|
||||
*/
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return getChannel() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called to determine whether manual launching of the agent is allowed at this point in time.
|
||||
* @return {@code true} if manual launching of the agent is allowed at this point in time.
|
||||
|
|
|
@ -38,7 +38,10 @@ import hudson.security.PermissionScope;
|
|||
import hudson.util.Secret;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import jenkins.model.FullyNamed;
|
||||
import jenkins.model.FullyNamedModelObject;
|
||||
import jenkins.model.Jenkins;
|
||||
import jenkins.model.Named;
|
||||
import jenkins.search.SearchGroup;
|
||||
import jenkins.util.SystemProperties;
|
||||
import jenkins.util.io.OnMaster;
|
||||
|
@ -73,7 +76,7 @@ import org.kohsuke.stapler.StaplerRequest2;
|
|||
* @see Items
|
||||
* @see ItemVisitor
|
||||
*/
|
||||
public interface Item extends PersistenceRoot, SearchableModelObject, AccessControlled, OnMaster {
|
||||
public interface Item extends PersistenceRoot, FullyNamedModelObject, SearchableModelObject, FullyNamed, Named, AccessControlled, OnMaster {
|
||||
/**
|
||||
* Gets the parent that contains this item.
|
||||
*/
|
||||
|
@ -97,6 +100,8 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont
|
|||
*
|
||||
* @see #getFullName()
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
String getName();
|
||||
|
||||
/**
|
||||
|
@ -110,6 +115,8 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont
|
|||
*
|
||||
* @see jenkins.model.Jenkins#getItemByFullName(String,Class)
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
String getFullName();
|
||||
|
||||
/**
|
||||
|
@ -127,13 +134,6 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont
|
|||
@Override
|
||||
String getDisplayName();
|
||||
|
||||
/**
|
||||
* Works like {@link #getDisplayName()} but return
|
||||
* the full path that includes all the display names
|
||||
* of the ancestors.
|
||||
*/
|
||||
String getFullDisplayName();
|
||||
|
||||
/**
|
||||
* Gets the relative name to this item from the specified group.
|
||||
*
|
||||
|
|
|
@ -33,6 +33,8 @@ import java.util.List;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import jenkins.model.FullyNamed;
|
||||
import jenkins.model.FullyNamedModelObject;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
||||
/**
|
||||
|
@ -41,19 +43,7 @@ import org.springframework.security.access.AccessDeniedException;
|
|||
* @author Kohsuke Kawaguchi
|
||||
* @see ItemGroupMixIn
|
||||
*/
|
||||
public interface ItemGroup<T extends Item> extends PersistenceRoot, ModelObject {
|
||||
/**
|
||||
* Gets the full name of this {@link ItemGroup}.
|
||||
*
|
||||
* @see Item#getFullName()
|
||||
*/
|
||||
String getFullName();
|
||||
|
||||
/**
|
||||
* @see Item#getFullDisplayName()
|
||||
*/
|
||||
String getFullDisplayName();
|
||||
|
||||
public interface ItemGroup<T extends Item> extends FullyNamed, FullyNamedModelObject, PersistenceRoot {
|
||||
/**
|
||||
* Gets all the items in this collection in a read-only view.
|
||||
*/
|
||||
|
|
|
@ -77,6 +77,7 @@ import java.awt.Paint;
|
|||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
|
@ -210,9 +211,11 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
|
|||
// This code can be deleted after several Jenkins releases,
|
||||
// when it is likely that everyone is running a version equal or higher to this version.
|
||||
var buildDirPath = getBuildDir().toPath();
|
||||
if (Files.deleteIfExists(buildDirPath.resolve("legacyIds"))) {
|
||||
Path legacyIds = buildDirPath.resolve("legacyIds");
|
||||
if (Files.exists(legacyIds)) {
|
||||
LOGGER.info("Deleting legacyIds file in " + buildDirPath + ". See https://issues.jenkins"
|
||||
+ ".io/browse/JENKINS-75465 for more information.");
|
||||
Files.delete(legacyIds);
|
||||
}
|
||||
|
||||
TextFile f = getNextBuildNumberFile();
|
||||
|
|
|
@ -26,11 +26,11 @@ package hudson.model;
|
|||
|
||||
import hudson.Extension;
|
||||
import hudson.Util;
|
||||
import hudson.util.HudsonIsLoading;
|
||||
import hudson.util.HudsonIsRestarting;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import jenkins.management.AdministrativeMonitorsDecorator;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import jenkins.management.Badge;
|
||||
import jenkins.model.Jenkins;
|
||||
import jenkins.model.ModelObjectWithContextMenu;
|
||||
|
@ -50,6 +50,9 @@ import org.kohsuke.stapler.StaplerResponse2;
|
|||
*/
|
||||
@Extension(ordinal = 998) @Symbol("manageJenkins")
|
||||
public class ManageJenkinsAction implements RootAction, StaplerFallback, ModelObjectWithContextMenu {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(ManageJenkinsAction.class.getName());
|
||||
|
||||
@Override
|
||||
public String getIconFileName() {
|
||||
if (Jenkins.get().hasAnyPermission(Jenkins.MANAGE, Jenkins.SYSTEM_READ))
|
||||
|
@ -98,28 +101,36 @@ public class ManageJenkinsAction implements RootAction, StaplerFallback, ModelOb
|
|||
menu.add("manage/" + url, icon, iconXml, text, post, requiresConfirmation, badge, message);
|
||||
}
|
||||
|
||||
/** Unlike {@link Jenkins#getActiveAdministrativeMonitors} this checks for activation lazily. */
|
||||
@Override
|
||||
public Badge getBadge() {
|
||||
Jenkins jenkins = Jenkins.get();
|
||||
AdministrativeMonitorsDecorator decorator = jenkins.getExtensionList(PageDecorator.class)
|
||||
.get(AdministrativeMonitorsDecorator.class);
|
||||
|
||||
if (decorator == null) {
|
||||
if (!(AdministrativeMonitor.hasPermissionToDisplay())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Collection<AdministrativeMonitor> activeAdministrativeMonitors = Optional.ofNullable(decorator.getMonitorsToDisplay()).orElse(Collections.emptyList());
|
||||
boolean anySecurity = activeAdministrativeMonitors.stream().anyMatch(AdministrativeMonitor::isSecurity);
|
||||
|
||||
if (activeAdministrativeMonitors.isEmpty()) {
|
||||
var app = Jenkins.get().getServletContext().getAttribute("app");
|
||||
if (app instanceof HudsonIsLoading || app instanceof HudsonIsRestarting) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int size = activeAdministrativeMonitors.size();
|
||||
String tooltip = size > 1 ? Messages.ManageJenkinsAction_notifications(size) : Messages.ManageJenkinsAction_notification(size);
|
||||
|
||||
return new Badge(String.valueOf(size),
|
||||
tooltip,
|
||||
anySecurity ? Badge.Severity.DANGER : Badge.Severity.WARNING);
|
||||
if (Jenkins.get().administrativeMonitors.stream().anyMatch(m -> m.isSecurity() && isActive(m))) {
|
||||
return new Badge("1+", Messages.ManageJenkinsAction_notifications(),
|
||||
Badge.Severity.DANGER);
|
||||
} else if (Jenkins.get().administrativeMonitors.stream().anyMatch(m -> !m.isSecurity() && isActive(m))) {
|
||||
return new Badge("1+", Messages.ManageJenkinsAction_notifications(),
|
||||
Badge.Severity.WARNING);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isActive(AdministrativeMonitor m) {
|
||||
try {
|
||||
return !m.isActivationFake() && m.hasRequiredPermission() && m.isEnabled() && m.isActivated();
|
||||
} catch (Throwable x) {
|
||||
LOGGER.log(Level.WARNING, null, x);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -109,6 +109,8 @@ import java.util.logging.Level;
|
|||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
import jenkins.console.WithConsoleUrl;
|
||||
import jenkins.model.FullyNamed;
|
||||
import jenkins.model.FullyNamedModelObject;
|
||||
import jenkins.model.Jenkins;
|
||||
import jenkins.model.queue.AsynchronousExecution;
|
||||
import jenkins.model.queue.CompositeCauseOfBlockage;
|
||||
|
@ -1882,7 +1884,7 @@ public class Queue extends ResourceController implements Saveable {
|
|||
* design, a {@link Task} must have at least one sub-task.)
|
||||
* Most of the time, the primary subtask is the only sub task.
|
||||
*/
|
||||
public interface Task extends ModelObject, SubTask {
|
||||
public interface Task extends FullyNamedModelObject, SubTask {
|
||||
/**
|
||||
* Returns true if the execution should be blocked
|
||||
* for temporary reasons.
|
||||
|
@ -1928,21 +1930,22 @@ public class Queue extends ResourceController implements Saveable {
|
|||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* @see hudson.model.Item#getFullDisplayName()
|
||||
*/
|
||||
String getFullDisplayName();
|
||||
|
||||
/**
|
||||
* Returns task-specific key which is used by the {@link LoadBalancer} to choose one particular executor
|
||||
* amongst all the free executors on all possibly suitable nodes.
|
||||
* NOTE: To be able to re-use the same node during the next run this key should not change from one run to
|
||||
* another. You probably want to compute that key based on the job's name.
|
||||
*
|
||||
* @return by default: {@link #getFullDisplayName()}
|
||||
* @return by default: {@link FullyNamed#getFullName()} if implements {@link FullyNamed} or {@link #getFullDisplayName()} otherwise.
|
||||
* @see hudson.model.LoadBalancer
|
||||
*/
|
||||
default String getAffinityKey() { return getFullDisplayName(); }
|
||||
default String getAffinityKey() {
|
||||
if (this instanceof FullyNamed fullyNamed) {
|
||||
return fullyNamed.getFullName();
|
||||
} else {
|
||||
return getFullDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the permission to see if the current user can abort this executable.
|
||||
|
|
|
@ -29,7 +29,7 @@ package hudson.model;
|
|||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public interface ResourceActivity {
|
||||
public interface ResourceActivity extends ModelObject {
|
||||
/**
|
||||
* Gets the list of {@link Resource}s that this task requires.
|
||||
* Used to make sure no two conflicting tasks run concurrently.
|
||||
|
@ -47,9 +47,4 @@ public interface ResourceActivity {
|
|||
default ResourceList getResourceList() {
|
||||
return ResourceList.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for rendering HTML.
|
||||
*/
|
||||
String getDisplayName();
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
|
|||
* Used to create new instance of {@link Run}.
|
||||
* @since 2.451
|
||||
*/
|
||||
public RunMap(@NonNull Job<?, ?> job, Constructor cons) {
|
||||
public RunMap(@NonNull Job<?, ?> job, Constructor<R> cons) {
|
||||
this.job = Objects.requireNonNull(job);
|
||||
this.cons = cons;
|
||||
initBaseDir(job.getBuildDir());
|
||||
|
@ -105,7 +105,7 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
|
|||
* @deprecated Use {@link #RunMap(Job, Constructor)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public RunMap(File baseDir, Constructor cons) {
|
||||
public RunMap(File baseDir, Constructor<R> cons) {
|
||||
job = null;
|
||||
this.cons = cons;
|
||||
initBaseDir(baseDir);
|
||||
|
@ -187,6 +187,8 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
|
|||
*/
|
||||
public interface Constructor<R extends Run<?, R>> {
|
||||
R create(File dir) throws IOException;
|
||||
|
||||
Class<R> getBuildClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -201,7 +203,7 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
|
|||
|
||||
/**
|
||||
* Add a <em>new</em> build to the map.
|
||||
* Do not use when loading existing builds (use {@link #put(Integer, Object)}).
|
||||
* Do not use when loading existing builds (use {@link #putAll(Map)}).
|
||||
*/
|
||||
@Override
|
||||
public R put(R r) {
|
||||
|
@ -298,6 +300,11 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<R> getBuildClass() {
|
||||
return cons.getBuildClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Backward compatibility method that notifies {@link RunMap} of who the owner is.
|
||||
*
|
||||
|
|
|
@ -52,12 +52,15 @@ import jakarta.servlet.ServletException;
|
|||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
@ -67,12 +70,14 @@ import java.util.concurrent.ExecutionException;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
import jenkins.model.IdStrategy;
|
||||
import jenkins.model.Jenkins;
|
||||
import jenkins.model.Loadable;
|
||||
import jenkins.model.ModelObjectWithContextMenu;
|
||||
import jenkins.scm.RunWithSCM;
|
||||
import jenkins.search.SearchGroup;
|
||||
import jenkins.security.HMACConfidentialKey;
|
||||
import jenkins.security.ImpersonatingUserDetailsService2;
|
||||
import jenkins.security.LastGrantedAuthoritiesProperty;
|
||||
import jenkins.security.UserDetailsCache;
|
||||
|
@ -174,7 +179,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
|
||||
@SuppressFBWarnings(value = "SS_SHOULD_BE_STATIC", justification = "Reserved for future use")
|
||||
private final int version = 10; // Not currently used, but it may be helpful in the future to store a version.
|
||||
private String id;
|
||||
String id;
|
||||
private volatile String fullName;
|
||||
private volatile String description;
|
||||
|
||||
|
@ -185,6 +190,8 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
XSTREAM.alias("user", User.class);
|
||||
}
|
||||
|
||||
private User() {}
|
||||
|
||||
private User(String id, String fullName) {
|
||||
this.id = id;
|
||||
this.fullName = fullName;
|
||||
|
@ -199,6 +206,10 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
private void load(String userId) {
|
||||
clearExistingProperties();
|
||||
loadFromUserConfigFile(userId);
|
||||
fixUpAfterLoad();
|
||||
}
|
||||
|
||||
private void fixUpAfterLoad() {
|
||||
removeNullsThatFailedToLoad();
|
||||
allocateDefaultPropertyInstancesAsNeeded();
|
||||
setUserToProperties();
|
||||
|
@ -225,9 +236,10 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
}
|
||||
|
||||
private void loadFromUserConfigFile(String userId) {
|
||||
AllUsers.getInstance().migrateUserIdMapper();
|
||||
XmlFile config = getConfigFile();
|
||||
try {
|
||||
if (config != null && config.exists()) {
|
||||
if (config.exists()) {
|
||||
config.unmarshal(this);
|
||||
this.id = userId;
|
||||
}
|
||||
|
@ -241,8 +253,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
}
|
||||
|
||||
private XmlFile getConfigFile() {
|
||||
File existingUserFolder = getExistingUserFolder();
|
||||
return existingUserFolder == null ? null : new XmlFile(XSTREAM, new File(existingUserFolder, CONFIG_XML));
|
||||
return new XmlFile(XSTREAM, new File(getUserFolderFor(id), CONFIG_XML));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -571,10 +582,10 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
*/
|
||||
private static @Nullable User getOrCreateById(@NonNull String id, @NonNull String fullName, boolean create) {
|
||||
User u = AllUsers.get(id);
|
||||
if (u == null && (create || UserIdMapper.getInstance().isMapped(id))) {
|
||||
if (u == null && create) {
|
||||
u = new User(id, fullName);
|
||||
AllUsers.put(id, u);
|
||||
if (!id.equals(fullName) && !UserIdMapper.getInstance().isMapped(id)) {
|
||||
if (!id.equals(fullName)) {
|
||||
try {
|
||||
u.save();
|
||||
} catch (IOException x) {
|
||||
|
@ -691,7 +702,6 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
*/
|
||||
@Restricted(Beta.class)
|
||||
public static void reload() throws IOException {
|
||||
UserIdMapper.getInstance().reload();
|
||||
AllUsers.reload();
|
||||
}
|
||||
|
||||
|
@ -708,6 +718,32 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
or greater issues in the realm change, could affect currently logged
|
||||
in users and even the user making the change. */
|
||||
try {
|
||||
var subdirectories = getRootDir().listFiles();
|
||||
if (subdirectories != null) {
|
||||
for (var oldDirectory : subdirectories) {
|
||||
var dirName = oldDirectory.getName();
|
||||
if (!HASHED_DIRNAMES.matcher(dirName).matches()) {
|
||||
continue;
|
||||
}
|
||||
var xml = new XmlFile(XSTREAM, new File(oldDirectory, CONFIG_XML));
|
||||
if (!xml.exists()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
var user = (User) xml.read();
|
||||
if (user.id == null) {
|
||||
continue;
|
||||
}
|
||||
var newDirectory = getUserFolderFor(user.id);
|
||||
if (!oldDirectory.equals(newDirectory)) {
|
||||
Files.move(oldDirectory.toPath(), newDirectory.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
LOGGER.info(() -> "migrated " + oldDirectory + " to " + newDirectory);
|
||||
}
|
||||
} catch (Exception x) {
|
||||
LOGGER.log(Level.WARNING, "failed to migrate " + xml, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
reload();
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to perform rekey operation.", e);
|
||||
|
@ -777,17 +813,9 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
if (ExtensionList.lookup(AllUsers.class).isEmpty()) {
|
||||
return;
|
||||
}
|
||||
UserIdMapper.getInstance().clear();
|
||||
AllUsers.clear();
|
||||
}
|
||||
|
||||
private static File getConfigFileFor(String id) {
|
||||
return new File(getUserFolderFor(id), "config.xml");
|
||||
}
|
||||
|
||||
private static File getUserFolderFor(String id) {
|
||||
return new File(getRootDir(), idStrategy().filenameOf(id));
|
||||
}
|
||||
/**
|
||||
* Returns the folder that store all the user information.
|
||||
* Useful for plugins to save a user-specific file aside the config.xml.
|
||||
|
@ -799,11 +827,8 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
*/
|
||||
|
||||
public @CheckForNull File getUserFolder() {
|
||||
return getExistingUserFolder();
|
||||
}
|
||||
|
||||
private @CheckForNull File getExistingUserFolder() {
|
||||
return UserIdMapper.getInstance().getDirectory(id);
|
||||
var d = getUserFolderFor(id);
|
||||
return d.isDirectory() ? d : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -813,6 +838,21 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
return new File(Jenkins.get().getRootDir(), "users");
|
||||
}
|
||||
|
||||
private static final int PREFIX_MAX = 14;
|
||||
private static final Pattern DISALLOWED_PREFIX_CHARS = Pattern.compile("[^A-Za-z0-9]");
|
||||
static final Pattern HASHED_DIRNAMES = Pattern.compile("[a-z0-9]{0," + PREFIX_MAX + "}_[a-f0-9]{64}");
|
||||
private static final HMACConfidentialKey DIRNAMES = new HMACConfidentialKey(User.class, "DIRNAMES");
|
||||
|
||||
private static String getUserFolderNameFor(String id) {
|
||||
var fullPrefix = DISALLOWED_PREFIX_CHARS.matcher(id).replaceAll("").toLowerCase(Locale.ROOT);
|
||||
return (fullPrefix.length() > PREFIX_MAX ? fullPrefix.substring(0, PREFIX_MAX) : fullPrefix) + '_' + DIRNAMES.mac(idStrategy().keyFor(id));
|
||||
}
|
||||
|
||||
@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "sanitized")
|
||||
static File getUserFolderFor(String id) {
|
||||
return new File(getRootDir(), getUserFolderNameFor(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the ID allowed? Some are prohibited for security reasons. See SECURITY-166.
|
||||
* <p>
|
||||
|
@ -852,19 +892,11 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
if (BulkChange.contains(this)) {
|
||||
return;
|
||||
}
|
||||
XmlFile xmlFile = new XmlFile(XSTREAM, constructUserConfigFile());
|
||||
XmlFile xmlFile = getConfigFile();
|
||||
xmlFile.write(this);
|
||||
SaveableListener.fireOnChange(this, xmlFile);
|
||||
}
|
||||
|
||||
private File constructUserConfigFile() throws IOException {
|
||||
return new File(putUserFolderIfAbsent(), CONFIG_XML);
|
||||
}
|
||||
|
||||
private File putUserFolderIfAbsent() throws IOException {
|
||||
return UserIdMapper.getInstance().putIfAbsent(id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the data directory and removes this user from Hudson.
|
||||
*
|
||||
|
@ -872,19 +904,11 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
*/
|
||||
public void delete() throws IOException {
|
||||
String idKey = idStrategy().keyFor(id);
|
||||
File existingUserFolder = getExistingUserFolder();
|
||||
UserIdMapper.getInstance().remove(id);
|
||||
AllUsers.remove(id);
|
||||
deleteExistingUserFolder(existingUserFolder);
|
||||
Util.deleteRecursive(getUserFolderFor(id));
|
||||
UserDetailsCache.get().invalidate(idKey);
|
||||
}
|
||||
|
||||
private void deleteExistingUserFolder(File existingUserFolder) throws IOException {
|
||||
if (existingUserFolder != null && existingUserFolder.exists()) {
|
||||
Util.deleteRecursive(existingUserFolder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposed remote API.
|
||||
*/
|
||||
|
@ -947,7 +971,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
public boolean canDelete() {
|
||||
final IdStrategy strategy = idStrategy();
|
||||
return hasPermission(Jenkins.ADMINISTER) && !strategy.equals(id, Jenkins.getAuthentication2().getName())
|
||||
&& UserIdMapper.getInstance().isMapped(id);
|
||||
&& getUserFolder() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1074,16 +1098,70 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
@Restricted(NoExternalUse.class)
|
||||
public static final class AllUsers {
|
||||
|
||||
private boolean migratedUserIdMapper;
|
||||
private final ConcurrentMap<String, User> byName = new ConcurrentHashMap<>();
|
||||
|
||||
@Initializer(after = InitMilestone.JOB_CONFIG_ADAPTED)
|
||||
public static void scanAll() {
|
||||
for (String userId : UserIdMapper.getInstance().getConvertedUserIds()) {
|
||||
User user = new User(userId, userId);
|
||||
getInstance().byName.putIfAbsent(idStrategy().keyFor(userId), user);
|
||||
@SuppressWarnings("deprecation")
|
||||
synchronized void migrateUserIdMapper() {
|
||||
if (!migratedUserIdMapper) {
|
||||
try {
|
||||
UserIdMapper.migrate();
|
||||
} catch (IOException x) {
|
||||
LOGGER.log(Level.WARNING, null, x);
|
||||
}
|
||||
migratedUserIdMapper = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Initializer(after = InitMilestone.JOB_CONFIG_ADAPTED)
|
||||
public static void scanAll() throws IOException {
|
||||
DIRNAMES.createMac(); // force the key to be saved during startup
|
||||
var instance = getInstance();
|
||||
instance.migrateUserIdMapper();
|
||||
var subdirectories = getRootDir().listFiles();
|
||||
if (subdirectories == null) {
|
||||
return;
|
||||
}
|
||||
var byName = instance.byName;
|
||||
var idStrategy = idStrategy();
|
||||
for (var dir : subdirectories) {
|
||||
var dirName = dir.getName();
|
||||
if (!HASHED_DIRNAMES.matcher(dirName).matches()) {
|
||||
LOGGER.fine(() -> "ignoring unrecognized dir " + dir);
|
||||
continue;
|
||||
}
|
||||
var xml = new XmlFile(XSTREAM, new File(dir, CONFIG_XML));
|
||||
if (!xml.exists()) {
|
||||
LOGGER.fine(() -> "ignoring dir " + dir + " with no " + CONFIG_XML);
|
||||
continue;
|
||||
}
|
||||
var user = new User();
|
||||
try {
|
||||
xml.unmarshal(user);
|
||||
} catch (Exception x) {
|
||||
LOGGER.log(Level.WARNING, "failed to load " + xml, x);
|
||||
continue;
|
||||
}
|
||||
if (user.id == null) {
|
||||
LOGGER.warning(() -> "ignoring " + xml + " with no <id>");
|
||||
continue;
|
||||
}
|
||||
var expectedFolderName = getUserFolderNameFor(user.id);
|
||||
if (!dirName.equals(expectedFolderName)) {
|
||||
LOGGER.warning(() -> "ignoring " + xml + " with <id> " + user.id + " expected to be in " + expectedFolderName);
|
||||
continue;
|
||||
}
|
||||
user.fixUpAfterLoad();
|
||||
var old = byName.put(idStrategy.keyFor(user.id), user);
|
||||
if (old != null) {
|
||||
LOGGER.warning(() -> "entry for " + user.id + " in " + dir + " duplicates one seen earlier for " + old.id);
|
||||
} else {
|
||||
LOGGER.fine(() -> "successfully loaded " + user.id + " from " + xml);
|
||||
}
|
||||
}
|
||||
LOGGER.fine(() -> "loaded " + byName.size() + " entries");
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyed by {@link User#id}. This map is used to ensure
|
||||
* singleton-per-id semantics of {@link User} objects.
|
||||
|
@ -1094,7 +1172,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
return ExtensionList.lookupSingleton(AllUsers.class);
|
||||
}
|
||||
|
||||
private static void reload() {
|
||||
private static void reload() throws IOException {
|
||||
getInstance().byName.clear();
|
||||
UserDetailsCache.get().invalidateAll();
|
||||
scanAll();
|
||||
|
@ -1252,7 +1330,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr
|
|||
UserDetails userDetails = UserDetailsCache.get().loadUserByUsername(idOrFullName);
|
||||
return userDetails.getUsername();
|
||||
} catch (UsernameNotFoundException x) {
|
||||
LOGGER.log(Level.FINE, "not sure whether " + idOrFullName + " is a valid username or not", x);
|
||||
LOGGER.log(Level.FINER, "not sure whether " + idOrFullName + " is a valid username or not", x);
|
||||
} catch (ExecutionException x) {
|
||||
LOGGER.log(Level.FINE, "could not look up " + idOrFullName, x);
|
||||
} finally {
|
||||
|
|
|
@ -24,179 +24,90 @@
|
|||
|
||||
package hudson.model;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import hudson.Extension;
|
||||
import hudson.ExtensionList;
|
||||
import hudson.Util;
|
||||
import hudson.XmlFile;
|
||||
import hudson.init.InitMilestone;
|
||||
import hudson.init.Initializer;
|
||||
import hudson.util.XStream2;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
import jenkins.model.IdStrategy;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
||||
/**
|
||||
* @deprecated Formerly used to track which directory held each user configuration.
|
||||
* Now that is deterministic based on a hash of {@link IdStrategy#keyFor}.
|
||||
*/
|
||||
@Deprecated
|
||||
@Restricted(NoExternalUse.class)
|
||||
@Extension
|
||||
public class UserIdMapper {
|
||||
|
||||
private static final XStream2 XSTREAM = new XStream2();
|
||||
static final String MAPPING_FILE = "users.xml";
|
||||
private static final Logger LOGGER = Logger.getLogger(UserIdMapper.class.getName());
|
||||
private static final int PREFIX_MAX = 15;
|
||||
private static final Pattern PREFIX_PATTERN = Pattern.compile("[^A-Za-z0-9]");
|
||||
@SuppressFBWarnings(value = "SS_SHOULD_BE_STATIC", justification = "Reserved for future use")
|
||||
private final int version = 1; // Not currently used, but it may be helpful in the future to store a version.
|
||||
|
||||
private transient File usersDirectory;
|
||||
// contrary to the name, the keys were actually IdStrategy.keyFor, not necessarily ids
|
||||
private Map<String, String> idToDirectoryNameMap = new ConcurrentHashMap<>();
|
||||
|
||||
static UserIdMapper getInstance() {
|
||||
return ExtensionList.lookupSingleton(UserIdMapper.class);
|
||||
private UserIdMapper() {
|
||||
}
|
||||
|
||||
public UserIdMapper() {
|
||||
}
|
||||
|
||||
@Initializer(after = InitMilestone.PLUGINS_STARTED, before = InitMilestone.JOB_LOADED)
|
||||
public File init() throws IOException {
|
||||
usersDirectory = createUsersDirectoryAsNeeded();
|
||||
load();
|
||||
return usersDirectory;
|
||||
}
|
||||
|
||||
@CheckForNull File getDirectory(String userId) {
|
||||
String directoryName = idToDirectoryNameMap.get(getIdStrategy().keyFor(userId));
|
||||
return directoryName == null ? null : new File(usersDirectory, directoryName);
|
||||
}
|
||||
|
||||
File putIfAbsent(String userId, boolean saveToDisk) throws IOException {
|
||||
String idKey = getIdStrategy().keyFor(userId);
|
||||
String directoryName = idToDirectoryNameMap.get(idKey);
|
||||
File directory = null;
|
||||
if (directoryName == null) {
|
||||
synchronized (this) {
|
||||
directoryName = idToDirectoryNameMap.get(idKey);
|
||||
if (directoryName == null) {
|
||||
directory = createDirectoryForNewUser(userId);
|
||||
directoryName = directory.getName();
|
||||
idToDirectoryNameMap.put(idKey, directoryName);
|
||||
if (saveToDisk) {
|
||||
save();
|
||||
@SuppressWarnings("deprecation")
|
||||
static void migrate() throws IOException {
|
||||
var idStrategy = User.idStrategy();
|
||||
var usersDirectory = User.getRootDir();
|
||||
var data = new UserIdMapper();
|
||||
var mapperXml = new XmlFile(XSTREAM, new File(usersDirectory, "users.xml"));
|
||||
if (mapperXml.exists()) { // need to migrate
|
||||
// Load it, and trust ids it defines over <id>…</id> in users/…/config.xml which UserIdMigrator neglected to resave.
|
||||
LOGGER.info(() -> "migrating " + mapperXml);
|
||||
mapperXml.unmarshal(data);
|
||||
for (var entry : data.idToDirectoryNameMap.entrySet()) {
|
||||
var idKey = entry.getKey();
|
||||
var directoryName = entry.getValue();
|
||||
try {
|
||||
var oldDirectory = new File(usersDirectory, directoryName);
|
||||
var userXml = new XmlFile(User.XSTREAM, new File(oldDirectory, User.CONFIG_XML));
|
||||
var user = (User) userXml.read();
|
||||
if (user.id == null || !idKey.equals(idStrategy.keyFor(user.id))) {
|
||||
user.id = idKey; // not quite right but hoping for the best
|
||||
userXml.write(user);
|
||||
}
|
||||
var newDirectory = User.getUserFolderFor(user.id);
|
||||
Files.move(oldDirectory.toPath(), newDirectory.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
LOGGER.info(() -> "migrated " + oldDirectory + " to " + newDirectory);
|
||||
} catch (Exception x) {
|
||||
LOGGER.log(Level.WARNING, "failed to migrate " + entry, x);
|
||||
}
|
||||
}
|
||||
mapperXml.delete();
|
||||
}
|
||||
return directory == null ? new File(usersDirectory, directoryName) : directory;
|
||||
}
|
||||
|
||||
boolean isMapped(String userId) {
|
||||
return idToDirectoryNameMap.containsKey(getIdStrategy().keyFor(userId));
|
||||
}
|
||||
|
||||
Set<String> getConvertedUserIds() {
|
||||
return Collections.unmodifiableSet(idToDirectoryNameMap.keySet());
|
||||
}
|
||||
|
||||
void remove(String userId) throws IOException {
|
||||
idToDirectoryNameMap.remove(getIdStrategy().keyFor(userId));
|
||||
save();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
idToDirectoryNameMap.clear();
|
||||
}
|
||||
|
||||
void reload() throws IOException {
|
||||
clear();
|
||||
load();
|
||||
}
|
||||
|
||||
protected IdStrategy getIdStrategy() {
|
||||
return User.idStrategy();
|
||||
}
|
||||
|
||||
protected File getUsersDirectory() {
|
||||
return User.getRootDir();
|
||||
}
|
||||
|
||||
private XmlFile getXmlConfigFile() {
|
||||
File file = getConfigFile(usersDirectory);
|
||||
return new XmlFile(XSTREAM, file);
|
||||
}
|
||||
|
||||
static File getConfigFile(File usersDirectory) {
|
||||
return new File(usersDirectory, MAPPING_FILE);
|
||||
}
|
||||
|
||||
private File createDirectoryForNewUser(String userId) throws IOException {
|
||||
try {
|
||||
Path tempDirectory = Files.createTempDirectory(Util.fileToPath(usersDirectory), generatePrefix(userId));
|
||||
return tempDirectory.toFile();
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "Error creating directory for user: " + userId, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private String generatePrefix(String userId) {
|
||||
String fullPrefix = PREFIX_PATTERN.matcher(userId).replaceAll("");
|
||||
return fullPrefix.length() > PREFIX_MAX - 1 ? fullPrefix.substring(0, PREFIX_MAX - 1) + '_' : fullPrefix + '_';
|
||||
}
|
||||
|
||||
private File createUsersDirectoryAsNeeded() throws IOException {
|
||||
File usersDirectory = getUsersDirectory();
|
||||
if (!usersDirectory.exists()) {
|
||||
try {
|
||||
Files.createDirectory(usersDirectory.toPath());
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.SEVERE, "Unable to create users directory: " + usersDirectory, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return usersDirectory;
|
||||
}
|
||||
|
||||
synchronized void save() throws IOException {
|
||||
try {
|
||||
getXmlConfigFile().write(this);
|
||||
} catch (IOException ioe) {
|
||||
LOGGER.log(Level.WARNING, "Error saving userId mapping file.", ioe);
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
|
||||
private void load() throws IOException {
|
||||
UserIdMigrator migrator = new UserIdMigrator(usersDirectory, getIdStrategy());
|
||||
if (migrator.needsMigration()) {
|
||||
try {
|
||||
migrator.migrateUsers(this);
|
||||
} catch (IOException ioe) {
|
||||
LOGGER.log(Level.SEVERE, "Error migrating users.", ioe);
|
||||
throw ioe;
|
||||
}
|
||||
} else {
|
||||
XmlFile config = getXmlConfigFile();
|
||||
try {
|
||||
config.unmarshal(this);
|
||||
} catch (NoSuchFileException e) {
|
||||
LOGGER.log(Level.FINE, "User id mapping file does not exist. It will be created when a user is saved.");
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Failed to load " + config, e);
|
||||
throw e;
|
||||
// Also look for any remaining user dirs, such as those predating even UserIdMapper, or PresetData or incomplete @LocalData.
|
||||
var subdirectories = usersDirectory.listFiles();
|
||||
if (subdirectories != null) {
|
||||
for (var oldDirectory : subdirectories) {
|
||||
if (!User.HASHED_DIRNAMES.matcher(oldDirectory.getName()).matches()) {
|
||||
var userXml = new XmlFile(User.XSTREAM, new File(oldDirectory, User.CONFIG_XML));
|
||||
if (userXml.exists()) {
|
||||
try {
|
||||
var user = (User) userXml.read();
|
||||
var id = user.id;
|
||||
if (id == null) {
|
||||
id = idStrategy.idFromFilename(oldDirectory.getName());
|
||||
user.id = id;
|
||||
userXml.write(user);
|
||||
}
|
||||
var newDirectory = User.getUserFolderFor(id);
|
||||
Files.move(oldDirectory.toPath(), newDirectory.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
LOGGER.info(() -> "migrated " + oldDirectory + " to " + newDirectory);
|
||||
} catch (Exception x) {
|
||||
LOGGER.log(Level.WARNING, "failed to migrate " + oldDirectory, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2018 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import jenkins.model.IdStrategy;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
||||
@Restricted(NoExternalUse.class)
|
||||
class UserIdMigrator {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(UserIdMigrator.class.getName());
|
||||
private static final String EMPTY_USERNAME_DIRECTORY_NAME = "emptyUsername";
|
||||
|
||||
private final File usersDirectory;
|
||||
private final IdStrategy idStrategy;
|
||||
|
||||
UserIdMigrator(File usersDirectory, IdStrategy idStrategy) {
|
||||
this.usersDirectory = usersDirectory;
|
||||
this.idStrategy = idStrategy;
|
||||
}
|
||||
|
||||
boolean needsMigration() {
|
||||
File mappingFile = UserIdMapper.getConfigFile(usersDirectory);
|
||||
if (mappingFile.exists() && mappingFile.isFile()) {
|
||||
LOGGER.finest("User mapping file already exists. No migration needed.");
|
||||
return false;
|
||||
}
|
||||
File[] userDirectories = listUserDirectories();
|
||||
return userDirectories != null && userDirectories.length > 0;
|
||||
}
|
||||
|
||||
private File[] listUserDirectories() {
|
||||
return usersDirectory.listFiles(file -> file.isDirectory() && new File(file, User.CONFIG_XML).exists());
|
||||
}
|
||||
|
||||
Map<String, File> scanExistingUsers() throws IOException {
|
||||
Map<String, File> users = new HashMap<>();
|
||||
File[] userDirectories = listUserDirectories();
|
||||
if (userDirectories != null) {
|
||||
for (File directory : userDirectories) {
|
||||
String userId = idStrategy.idFromFilename(directory.getName());
|
||||
users.put(userId, directory);
|
||||
}
|
||||
}
|
||||
addEmptyUsernameIfExists(users);
|
||||
return users;
|
||||
}
|
||||
|
||||
private void addEmptyUsernameIfExists(Map<String, File> users) throws IOException {
|
||||
File emptyUsernameConfigFile = new File(usersDirectory, User.CONFIG_XML);
|
||||
if (emptyUsernameConfigFile.exists()) {
|
||||
File newEmptyUsernameDirectory = new File(usersDirectory, EMPTY_USERNAME_DIRECTORY_NAME);
|
||||
Files.createDirectory(newEmptyUsernameDirectory.toPath());
|
||||
File newEmptyUsernameConfigFile = new File(newEmptyUsernameDirectory, User.CONFIG_XML);
|
||||
Files.move(emptyUsernameConfigFile.toPath(), newEmptyUsernameConfigFile.toPath());
|
||||
users.put("", newEmptyUsernameDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
void migrateUsers(UserIdMapper mapper) throws IOException {
|
||||
LOGGER.fine("Beginning migration of users to userId mapping.");
|
||||
Map<String, File> existingUsers = scanExistingUsers();
|
||||
for (Map.Entry<String, File> existingUser : existingUsers.entrySet()) {
|
||||
File newDirectory = mapper.putIfAbsent(existingUser.getKey(), false);
|
||||
LOGGER.log(Level.INFO, "Migrating user '" + existingUser.getKey() + "' from 'users/" + existingUser.getValue().getName() + "/' to 'users/" + newDirectory.getName() + "/'");
|
||||
Files.move(existingUser.getValue().toPath(), newDirectory.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
mapper.save();
|
||||
LOGGER.fine("Completed migration of users to userId mapping.");
|
||||
}
|
||||
|
||||
}
|
|
@ -27,6 +27,7 @@ package hudson.model.queue;
|
|||
import static java.lang.Math.max;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import hudson.model.Computer;
|
||||
import hudson.model.Executor;
|
||||
import hudson.model.Label;
|
||||
|
@ -48,6 +49,7 @@ import java.util.LinkedHashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
import jenkins.model.Named;
|
||||
|
||||
/**
|
||||
* Defines a mapping problem for answering "where do we execute this task?"
|
||||
|
@ -114,7 +116,7 @@ public class MappingWorksheet {
|
|||
}
|
||||
}
|
||||
|
||||
public final class ExecutorChunk extends ReadOnlyList<ExecutorSlot> {
|
||||
public final class ExecutorChunk extends ReadOnlyList<ExecutorSlot> implements Named {
|
||||
public final int index;
|
||||
public final Computer computer;
|
||||
public final Node node;
|
||||
|
@ -150,6 +152,8 @@ public class MappingWorksheet {
|
|||
/**
|
||||
* Node name.
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return node.getNodeName();
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ public class NodeProvisioner {
|
|||
LOGGER.log(Level.INFO,
|
||||
"{0} provisioning successfully completed. "
|
||||
+ "We have now {1,number,integer} computer(s)",
|
||||
new Object[]{f.displayName, jenkins.getComputers().length});
|
||||
new Object[]{f.displayName, jenkins.getComputersCollection().size()});
|
||||
fireOnCommit(f, node);
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.WARNING,
|
||||
|
|
|
@ -66,6 +66,7 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
|
@ -641,9 +642,11 @@ public class SlaveComputer extends Computer {
|
|||
// Orderly shutdown will have null exception
|
||||
if (cause != null) {
|
||||
offlineCause = new ChannelTermination(cause);
|
||||
Functions.printStackTrace(cause, taskListener.error("Connection terminated"));
|
||||
} else {
|
||||
}
|
||||
if (cause == null || cause instanceof ClosedChannelException) {
|
||||
taskListener.getLogger().println("Connection terminated");
|
||||
} else {
|
||||
Functions.printStackTrace(cause, taskListener.error("Connection terminated"));
|
||||
}
|
||||
closeChannel();
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package hudson.util;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
||||
/**
|
||||
*
|
||||
* ClassLoader with internal caching of class loading results.
|
||||
*
|
||||
* <p>
|
||||
* Caches both successful and failed class lookups to avoid redundant delegation
|
||||
* and repeated class resolution attempts. Designed for performance optimization
|
||||
* in systems that repeatedly query class presence (e.g., plugin environments,
|
||||
* reflective loading, optional dependencies).
|
||||
* </p>
|
||||
*
|
||||
* Useful for classloaders that have heavy-weight loadClass() implementations
|
||||
*
|
||||
* @author Dmytro Ukhlov
|
||||
*/
|
||||
@Restricted(NoExternalUse.class)
|
||||
public class CachingClassLoader extends DelegatingClassLoader {
|
||||
private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
|
||||
|
||||
public CachingClassLoader(String name, ClassLoader parent) {
|
||||
super(name, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
Object classOrEmpty = cache.computeIfAbsent(name, key -> {
|
||||
try {
|
||||
return super.loadClass(name, false);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// Not found.
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
|
||||
if (classOrEmpty == Optional.empty()) {
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
|
||||
Class<?> clazz = (Class<?>) classOrEmpty;
|
||||
if (resolve) {
|
||||
resolveClass(clazz);
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public void clearCacheMisses() {
|
||||
cache.values().removeIf(v -> v == Optional.empty());
|
||||
}
|
||||
}
|
|
@ -37,7 +37,10 @@ import java.util.Comparator;
|
|||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
|
@ -64,15 +67,23 @@ public abstract class CopyOnWriteMap<K, V> implements Map<K, V> {
|
|||
update(Collections.emptyMap());
|
||||
}
|
||||
|
||||
protected Map<K, V> getView() {
|
||||
return view;
|
||||
}
|
||||
|
||||
protected Map<K, V> createView() {
|
||||
return Collections.unmodifiableMap(core);
|
||||
}
|
||||
|
||||
protected void update(Map<K, V> m) {
|
||||
core = m;
|
||||
view = Collections.unmodifiableMap(core);
|
||||
view = createView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically replaces the entire map by the copy of the specified map.
|
||||
*/
|
||||
public void replaceBy(Map<? extends K, ? extends V> data) {
|
||||
public synchronized void replaceBy(Map<? extends K, ? extends V> data) {
|
||||
Map<K, V> d = copy();
|
||||
d.clear();
|
||||
d.putAll(data);
|
||||
|
@ -214,7 +225,7 @@ public abstract class CopyOnWriteMap<K, V> implements Map<K, V> {
|
|||
/**
|
||||
* {@link CopyOnWriteMap} backed by {@link TreeMap}.
|
||||
*/
|
||||
public static final class Tree<K, V> extends CopyOnWriteMap<K, V> {
|
||||
public static final class Tree<K, V> extends CopyOnWriteMap<K, V> implements NavigableMap<K, V> {
|
||||
private final Comparator<K> comparator;
|
||||
|
||||
public Tree(Map<K, V> core, Comparator<K> comparator) {
|
||||
|
@ -232,7 +243,7 @@ public abstract class CopyOnWriteMap<K, V> implements Map<K, V> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected Map<K, V> copy() {
|
||||
protected TreeMap<K, V> copy() {
|
||||
TreeMap<K, V> m = new TreeMap<>(comparator);
|
||||
m.putAll(core);
|
||||
return m;
|
||||
|
@ -243,6 +254,142 @@ public abstract class CopyOnWriteMap<K, V> implements Map<K, V> {
|
|||
update(new TreeMap<>(comparator));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NavigableMap<K, V> createView() {
|
||||
return Collections.unmodifiableNavigableMap((NavigableMap<K, V>) core);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NavigableMap<K, V> getView() {
|
||||
return (NavigableMap<K, V>) super.getView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Entry<K, V> pollFirstEntry() {
|
||||
TreeMap<K, V> d = copy();
|
||||
Entry<K, V> res = d.pollFirstEntry();
|
||||
update(d);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Entry<K, V> pollLastEntry() {
|
||||
TreeMap<K, V> d = copy();
|
||||
Entry<K, V> res = d.pollLastEntry();
|
||||
update(d);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<K, V> lowerEntry(K key) {
|
||||
return getView().lowerEntry(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public K lowerKey(K key) {
|
||||
return getView().lowerKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<K, V> floorEntry(K key) {
|
||||
return getView().floorEntry(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public K floorKey(K key) {
|
||||
return getView().floorKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<K, V> ceilingEntry(K key) {
|
||||
return getView().ceilingEntry(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public K ceilingKey(K key) {
|
||||
return getView().ceilingKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<K, V> higherEntry(K key) {
|
||||
return getView().higherEntry(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public K higherKey(K key) {
|
||||
return getView().higherKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<K, V> firstEntry() {
|
||||
return getView().firstEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<K, V> lastEntry() {
|
||||
return getView().lastEntry();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableMap<K, V> descendingMap() {
|
||||
return getView().descendingMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableSet<K> navigableKeySet() {
|
||||
return getView().navigableKeySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableSet<K> descendingKeySet() {
|
||||
return getView().descendingKeySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
|
||||
return getView().subMap(fromKey, fromInclusive, toKey, toInclusive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
|
||||
return getView().headMap(toKey, inclusive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
|
||||
return getView().tailMap(fromKey, inclusive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<? super K> comparator() {
|
||||
return getView().comparator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedMap<K, V> subMap(K fromKey, K toKey) {
|
||||
return getView().subMap(fromKey, toKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedMap<K, V> headMap(K toKey) {
|
||||
return getView().headMap(toKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedMap<K, V> tailMap(K fromKey) {
|
||||
return getView().tailMap(fromKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public K firstKey() {
|
||||
return getView().firstKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public K lastKey() {
|
||||
return getView().lastKey();
|
||||
}
|
||||
|
||||
public static class ConverterImpl extends TreeMapConverter {
|
||||
public ConverterImpl(Mapper mapper) {
|
||||
super(mapper);
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package hudson.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
||||
/**
|
||||
* A {@link ClassLoader} that does not define any classes itself but delegates class loading to
|
||||
* other class loaders to avoid the JDK's per-class-name locking and lock retention.
|
||||
*
|
||||
* <p>This class first attempts to load classes via its {@link ClassLoader#getParent} class loader,
|
||||
* then falls back to {@link ClassLoader#findClass} to allow for custom delegation logic.
|
||||
*
|
||||
* <p>In a parallel-capable {@link ClassLoader}, the JDK maintains a per-name lock object
|
||||
* indefinitely. In Jenkins, many class loading misses across many loaders can accumulate hundreds
|
||||
* of thousands of such locks, retaining significant memory. This loader never defines classes and
|
||||
* bypasses {@link ClassLoader}'s default {@code loadClass} locking; it delegates to the parent
|
||||
* first and then to {@code findClass} for custom delegation.
|
||||
*
|
||||
* <p>The actual defining loader (parent or a delegate) still performs the necessary synchronization
|
||||
* and class definition. A runtime guard ({@link #verify}) throws if this loader ever becomes the
|
||||
* defining loader.
|
||||
*
|
||||
* <p>Subclasses must not call {@code defineClass}; implement delegation in {@code findClass} if
|
||||
* needed and do not mark subclasses as parallel-capable.
|
||||
*
|
||||
* @author Dmytro Ukhlov
|
||||
*/
|
||||
@Restricted(NoExternalUse.class)
|
||||
public abstract class DelegatingClassLoader extends ClassLoader {
|
||||
protected DelegatingClassLoader(String name, ClassLoader parent) {
|
||||
super(name, Objects.requireNonNull(parent));
|
||||
}
|
||||
|
||||
public DelegatingClassLoader(ClassLoader parent) {
|
||||
super(Objects.requireNonNull(parent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent-first delegation without synchronizing on {@link #getClassLoadingLock(String)}. This
|
||||
* prevents creation/retention of per-name lock objects in a loader that does not define
|
||||
* classes. The defining loader downstream still serializes class definition as required.
|
||||
*
|
||||
* @param name The binary name of the class
|
||||
* @param resolve If {@code true} then resolve the class
|
||||
* @return The resulting {@link Class} object
|
||||
* @throws ClassNotFoundException If the class could not be found
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
Class<?> c = null;
|
||||
try {
|
||||
c = getParent().loadClass(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
// ClassNotFoundException thrown if class not found
|
||||
// from the non-null parent class loader
|
||||
}
|
||||
|
||||
if (c == null) {
|
||||
// If still not found, then invoke findClass in order
|
||||
// to find the class.
|
||||
c = findClass(name);
|
||||
}
|
||||
|
||||
verify(c);
|
||||
if (resolve) {
|
||||
resolveClass(c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safety check to ensure this delegating loader never becomes the defining loader.
|
||||
*
|
||||
* <p>Fails fast if a subclass erroneously defines a class here, which would violate the
|
||||
* delegation-only contract and could reintroduce locking/retention issues.
|
||||
*/
|
||||
private void verify(Class<?> clazz) {
|
||||
if (clazz.getClassLoader() == this) {
|
||||
throw new IllegalStateException("DelegatingClassLoader must not be the defining loader: " + clazz.getName());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package hudson.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import jenkins.util.URLClassLoader2;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
||||
/**
|
||||
* A {@link ClassLoader} that verifies the existence of a {@code .class} resource before attempting
|
||||
* to load the class. Intended to sit in front of servlet container loaders we do not control.
|
||||
*
|
||||
* <p>This implementation overrides {@link #loadClass(String, boolean)} and uses {@link
|
||||
* #getResource(String)} to check whether the corresponding <code>.class</code> file is available in
|
||||
* the classpath. If the resource is not found, a {@link ClassNotFoundException} is thrown
|
||||
* immediately.
|
||||
*
|
||||
* <p>Parallel-capable parent loaders retain a per-class-name lock object for every load attempt,
|
||||
* including misses. By checking getResource(name + ".class") first and throwing {@link
|
||||
* ClassNotFoundException} on absence, we avoid calling {@code loadClass} on misses, thus preventing
|
||||
* the parent from populating its lock map for nonexistent classes.
|
||||
*
|
||||
* <p>This class is only needed in {@link hudson.PluginManager.UberClassLoader}. It is unnecessary
|
||||
* for plugin {@link ClassLoader}s (because {@link URLClassLoader2} mitigates lock retention via
|
||||
* {@link ClassLoader#getClassLoadingLock}) and redundant for delegators (because {@link
|
||||
* DelegatingClassLoader} already avoids base locking).
|
||||
*
|
||||
* @author Dmytro Ukhlov
|
||||
* @see ClassLoader
|
||||
* @see #getResource(String)
|
||||
*/
|
||||
@Restricted(NoExternalUse.class)
|
||||
public final class ExistenceCheckingClassLoader extends DelegatingClassLoader {
|
||||
|
||||
public ExistenceCheckingClassLoader(String name, ClassLoader parent) {
|
||||
super(name, Objects.requireNonNull(parent));
|
||||
}
|
||||
|
||||
public ExistenceCheckingClassLoader(ClassLoader parent) {
|
||||
super(Objects.requireNonNull(parent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Short-circuits misses by checking for the {@code .class} resource prior to delegation.
|
||||
* Successful loads behave exactly as the parent would; misses do not touch the parent's
|
||||
* per-name lock map.
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
// Add support for loading of JaCoCo dynamic instrumentation classes
|
||||
if (name.equals("java.lang.$JaCoCo")) {
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
|
||||
if (getResource(name.replace('.', '/') + ".class") == null) {
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ import java.util.ListIterator;
|
|||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
||||
|
@ -326,6 +327,18 @@ public class Iterators {
|
|||
return com.google.common.collect.Iterators.filter(itr, Objects::nonNull);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps another iterator and map iterable objects.
|
||||
*/
|
||||
public static <T, U> Iterator<U> map(final Iterator<T> itr, Function<T, U> mapper) {
|
||||
return new AdaptedIterator<>(itr) {
|
||||
@Override
|
||||
protected U adapt(T item) {
|
||||
return mapper.apply(item);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Iterable} that iterates over all the given {@link Iterable}s.
|
||||
*
|
||||
|
|
|
@ -42,7 +42,7 @@ import java.util.stream.Collectors;
|
|||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
public class MaskingClassLoader extends ClassLoader {
|
||||
public class MaskingClassLoader extends DelegatingClassLoader {
|
||||
/**
|
||||
* Prefix of the packages that should be hidden.
|
||||
*/
|
||||
|
@ -50,10 +50,6 @@ public class MaskingClassLoader extends ClassLoader {
|
|||
|
||||
private final List<String> masksResources;
|
||||
|
||||
static {
|
||||
registerAsParallelCapable();
|
||||
}
|
||||
|
||||
public MaskingClassLoader(ClassLoader parent, String... masks) {
|
||||
this(parent, Arrays.asList(masks));
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ Lesser General Public License for more details.
|
|||
package hudson.util.jna;
|
||||
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.TreeMap;
|
||||
|
@ -89,6 +90,7 @@ public class RegistryKey implements AutoCloseable {
|
|||
return convertBufferToInt(getValue(valueName));
|
||||
}
|
||||
|
||||
@SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH", justification = "TODO needs triage")
|
||||
private byte[] getValue(String valueName) {
|
||||
IntByReference pType, lpcbData;
|
||||
byte[] lpData = new byte[1];
|
||||
|
@ -149,6 +151,7 @@ public class RegistryKey implements AutoCloseable {
|
|||
/**
|
||||
* Does a specified value exist?
|
||||
*/
|
||||
@SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH", justification = "TODO needs triage")
|
||||
public boolean valueExists(String name) {
|
||||
IntByReference pType, lpcbData;
|
||||
byte[] lpData = new byte[1];
|
||||
|
@ -223,6 +226,7 @@ public class RegistryKey implements AutoCloseable {
|
|||
*
|
||||
* @return TreeMap with name and value pairs
|
||||
*/
|
||||
@SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH", justification = "TODO needs triage")
|
||||
public TreeMap<String, Object> getValues() {
|
||||
int dwIndex, result;
|
||||
char[] lpValueName;
|
||||
|
|
|
@ -141,7 +141,7 @@ public class CloudSet extends AbstractModelObject implements Describable<CloudSe
|
|||
@Override
|
||||
public ModelObjectWithContextMenu.ContextMenu doChildrenContextMenu(StaplerRequest2 request, StaplerResponse2 response) throws Exception {
|
||||
ModelObjectWithContextMenu.ContextMenu m = new ModelObjectWithContextMenu.ContextMenu();
|
||||
Jenkins.get().clouds.stream().forEach(m::add);
|
||||
Jenkins.get().clouds.forEach(m::add);
|
||||
return m;
|
||||
}
|
||||
|
||||
|
|
|
@ -137,10 +137,10 @@ public final class WebSocketAgents extends InvisibleAction implements Unprotecte
|
|||
@SuppressFBWarnings(value = "RV_RETURN_VALUE_IGNORED_BAD_PRACTICE", justification = "method signature does not permit plumbing through the return value")
|
||||
@Override
|
||||
protected void opened() {
|
||||
transport = new Transport();
|
||||
Computer.threadPoolForRemoting.submit(() -> {
|
||||
LOGGER.fine(() -> "setting up channel for " + agent);
|
||||
state.fireBeforeChannel(new ChannelBuilder(agent, Computer.threadPoolForRemoting));
|
||||
transport = new Transport();
|
||||
try {
|
||||
state.fireAfterChannel(state.getChannelBuilder().build(transport));
|
||||
LOGGER.fine(() -> "set up channel for " + agent);
|
||||
|
|
|
@ -31,6 +31,11 @@ public class URICheckEncodingMonitor extends AdministrativeMonitor {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActivationFake() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return Messages.URICheckEncodingMonitor_DisplayName();
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
package jenkins.management;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.model.PageDecorator;
|
||||
import hudson.model.RootAction;
|
||||
import jakarta.servlet.ServletException;
|
||||
import java.io.IOException;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
import org.kohsuke.stapler.StaplerRequest2;
|
||||
import org.kohsuke.stapler.StaplerResponse2;
|
||||
import org.kohsuke.stapler.verb.GET;
|
||||
|
||||
@Extension
|
||||
@Restricted(NoExternalUse.class)
|
||||
public class AdministrativeMonitorsApi implements RootAction {
|
||||
@GET
|
||||
public void doNonSecurityPopupContent(StaplerRequest2 req, StaplerResponse2 resp) throws IOException, ServletException {
|
||||
AdministrativeMonitorsApiData viewData = new AdministrativeMonitorsApiData(getDecorator().getNonSecurityAdministrativeMonitors());
|
||||
req.getView(viewData, "monitorsList.jelly").forward(req, resp);
|
||||
}
|
||||
|
||||
@GET
|
||||
public void doSecurityPopupContent(StaplerRequest2 req, StaplerResponse2 resp) throws IOException, ServletException {
|
||||
AdministrativeMonitorsApiData viewData = new AdministrativeMonitorsApiData(getDecorator().getSecurityAdministrativeMonitors());
|
||||
req.getView(viewData, "monitorsList.jelly").forward(req, resp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIconFileName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrlName() {
|
||||
return "administrativeMonitorsApi";
|
||||
}
|
||||
|
||||
private AdministrativeMonitorsDecorator getDecorator() {
|
||||
return Jenkins.get()
|
||||
.getExtensionList(PageDecorator.class)
|
||||
.get(AdministrativeMonitorsDecorator.class);
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package jenkins.management;
|
||||
|
||||
import hudson.model.AdministrativeMonitor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
||||
@Restricted(NoExternalUse.class)
|
||||
public class AdministrativeMonitorsApiData {
|
||||
private final List<AdministrativeMonitor> monitorsList = new ArrayList<>();
|
||||
|
||||
AdministrativeMonitorsApiData(List<AdministrativeMonitor> monitors) {
|
||||
monitorsList.addAll(monitors);
|
||||
}
|
||||
|
||||
public List<AdministrativeMonitor> getMonitorsList() {
|
||||
return this.monitorsList;
|
||||
}
|
||||
|
||||
public boolean hasActiveMonitors() {
|
||||
return !this.monitorsList.isEmpty();
|
||||
}
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2016, Daniel Beck, 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 jenkins.management;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import hudson.Extension;
|
||||
import hudson.diagnosis.ReverseProxySetupMonitor;
|
||||
import hudson.model.AdministrativeMonitor;
|
||||
import hudson.model.PageDecorator;
|
||||
import hudson.util.HudsonIsLoading;
|
||||
import hudson.util.HudsonIsRestarting;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import jenkins.diagnostics.URICheckEncodingMonitor;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
import org.kohsuke.stapler.Ancestor;
|
||||
import org.kohsuke.stapler.Stapler;
|
||||
import org.kohsuke.stapler.StaplerRequest2;
|
||||
|
||||
/**
|
||||
* Show notifications and popups for active administrative monitors on all pages.
|
||||
*/
|
||||
@Extension
|
||||
@Restricted(NoExternalUse.class)
|
||||
public class AdministrativeMonitorsDecorator extends PageDecorator {
|
||||
private final Collection<String> ignoredJenkinsRestOfUrls = new ArrayList<>();
|
||||
|
||||
public AdministrativeMonitorsDecorator() {
|
||||
// otherwise this would be added to every internal context menu building request
|
||||
ignoredJenkinsRestOfUrls.add("contextMenu");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return Messages.AdministrativeMonitorsDecorator_DisplayName();
|
||||
}
|
||||
|
||||
// Used by Jelly
|
||||
public Collection<AdministrativeMonitor> filterNonSecurityAdministrativeMonitors(Collection<AdministrativeMonitor> activeMonitors) {
|
||||
return this.filterActiveAdministrativeMonitors(activeMonitors, false);
|
||||
}
|
||||
|
||||
// Used by Jelly
|
||||
public Collection<AdministrativeMonitor> filterSecurityAdministrativeMonitors(Collection<AdministrativeMonitor> activeMonitors) {
|
||||
return this.filterActiveAdministrativeMonitors(activeMonitors, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent us to compute multiple times the {@link AdministrativeMonitor#isActivated()} by re-using the same list
|
||||
*/
|
||||
private Collection<AdministrativeMonitor> filterActiveAdministrativeMonitors(Collection<AdministrativeMonitor> activeMonitors, boolean isSecurity) {
|
||||
Collection<AdministrativeMonitor> active = new ArrayList<>();
|
||||
for (AdministrativeMonitor am : activeMonitors) {
|
||||
if (am.isSecurity() == isSecurity) {
|
||||
active.add(am);
|
||||
}
|
||||
}
|
||||
return active;
|
||||
}
|
||||
|
||||
// Used by API
|
||||
public List<AdministrativeMonitor> getNonSecurityAdministrativeMonitors() {
|
||||
Collection<AdministrativeMonitor> allowedMonitors = getMonitorsToDisplay();
|
||||
|
||||
if (allowedMonitors == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return allowedMonitors.stream()
|
||||
.filter(administrativeMonitor -> !administrativeMonitor.isSecurity())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// Used by API
|
||||
public List<AdministrativeMonitor> getSecurityAdministrativeMonitors() {
|
||||
Collection<AdministrativeMonitor> allowedMonitors = getMonitorsToDisplay();
|
||||
|
||||
if (allowedMonitors == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return allowedMonitors.stream()
|
||||
.filter(AdministrativeMonitor::isSecurity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Collection<AdministrativeMonitor> getAllActiveAdministrativeMonitors() {
|
||||
Collection<AdministrativeMonitor> active = new ArrayList<>();
|
||||
for (AdministrativeMonitor am : Jenkins.get().getActiveAdministrativeMonitors()) {
|
||||
if (am instanceof ReverseProxySetupMonitor) {
|
||||
// TODO make reverse proxy monitor work when shown on any URL
|
||||
continue;
|
||||
}
|
||||
if (am instanceof URICheckEncodingMonitor) {
|
||||
// TODO make URI encoding monitor work when shown on any URL
|
||||
continue;
|
||||
}
|
||||
active.add(am);
|
||||
}
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the administrative monitors that are active and should be shown.
|
||||
* This is done only when the instance is currently running and the user has the permission to read them.
|
||||
*
|
||||
* @return the list of active monitors if we should display them, otherwise null.
|
||||
*/
|
||||
public Collection<AdministrativeMonitor> getMonitorsToDisplay() {
|
||||
if (!(AdministrativeMonitor.hasPermissionToDisplay())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StaplerRequest2 req = Stapler.getCurrentRequest2();
|
||||
|
||||
if (req == null) {
|
||||
return null;
|
||||
}
|
||||
List<Ancestor> ancestors = req.getAncestors();
|
||||
|
||||
if (ancestors == null || ancestors.isEmpty()) {
|
||||
// ???
|
||||
return null;
|
||||
}
|
||||
|
||||
Ancestor a = ancestors.get(ancestors.size() - 1);
|
||||
Object o = a.getObject();
|
||||
|
||||
// don't show while Jenkins is loading
|
||||
if (o instanceof HudsonIsLoading || o instanceof HudsonIsRestarting) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// don't show for some URLs served directly by Jenkins
|
||||
if (o instanceof Jenkins) {
|
||||
String url = a.getRestOfUrl();
|
||||
|
||||
if (ignoredJenkinsRestOfUrls.contains(url)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return getAllActiveAdministrativeMonitors();
|
||||
}
|
||||
}
|
|
@ -66,7 +66,6 @@ public class Badge {
|
|||
*
|
||||
* @param text The text to be shown in the badge.
|
||||
* Keep it short, ideally just a number. More than 6 or 7 characters do not look good. Avoid spaces as they will lead to line breaks.
|
||||
* as this might lead to line breaks.
|
||||
* @param tooltip The tooltip to show for the badge.
|
||||
* Do not include html tags.
|
||||
* @param severity The severity of the badge (danger, warning, info)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package jenkins.model;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
|
||||
/**
|
||||
* An interface for objects that have a name and a parent, so exposing a full name.
|
||||
*/
|
||||
public interface FullyNamed {
|
||||
/**
|
||||
* Returns the full name of this object, which is a qualified name
|
||||
* that includes the names of all its ancestors, separated by '/'.
|
||||
*
|
||||
* @return the full name of this object.
|
||||
*/
|
||||
@NonNull
|
||||
String getFullName();
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package jenkins.model;
|
||||
|
||||
import hudson.model.ModelObject;
|
||||
import jenkins.security.stapler.StaplerAccessibleType;
|
||||
|
||||
/**
|
||||
* A model object that has a human-readable full name. This is usually valid when nested as part of an object hierarchy.
|
||||
*
|
||||
* <p>
|
||||
* This interface is used to mark objects that can be qualified in the context of a Jenkins instance.
|
||||
* It is typically used for objects that are part of the Jenkins model and can be referenced by their names.
|
||||
*
|
||||
* @see ModelObject
|
||||
*/
|
||||
@StaplerAccessibleType
|
||||
public interface FullyNamedModelObject extends ModelObject {
|
||||
/**
|
||||
* Works like {@link #getDisplayName()} but return
|
||||
* the full path that includes all the display names
|
||||
* of the ancestors in an unspecified format.
|
||||
*/
|
||||
String getFullDisplayName();
|
||||
}
|
|
@ -29,7 +29,6 @@ import edu.umd.cs.findbugs.annotations.NonNull;
|
|||
import hudson.markup.MarkupFormatter;
|
||||
import hudson.model.BallColor;
|
||||
import hudson.model.BuildBadgeAction;
|
||||
import hudson.model.ModelObject;
|
||||
import hudson.model.ParameterValue;
|
||||
import hudson.model.ParametersAction;
|
||||
import hudson.model.Queue;
|
||||
|
@ -48,7 +47,7 @@ import org.kohsuke.accmod.restrictions.Beta;
|
|||
* @since 2.477
|
||||
*/
|
||||
@Restricted(Beta.class)
|
||||
public interface HistoricalBuild extends ModelObject {
|
||||
public interface HistoricalBuild extends FullyNamedModelObject {
|
||||
|
||||
/**
|
||||
* @return A build number
|
||||
|
@ -74,12 +73,6 @@ public interface HistoricalBuild extends ModelObject {
|
|||
@CheckForNull
|
||||
String getDescription();
|
||||
|
||||
/**
|
||||
* @return a human-readable full display name of this build.
|
||||
*/
|
||||
@NonNull
|
||||
String getFullDisplayName();
|
||||
|
||||
/**
|
||||
* Get the {@link Queue.Item#getId()} of the original queue item from where this {@link HistoricalBuild} instance
|
||||
* originated.
|
||||
|
|
|
@ -27,7 +27,7 @@ package jenkins.model;
|
|||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import hudson.Util;
|
||||
import hudson.model.Computer;
|
||||
import hudson.model.Node;
|
||||
import hudson.model.ModelObject;
|
||||
import hudson.security.ACL;
|
||||
import hudson.security.AccessControlled;
|
||||
import java.util.List;
|
||||
|
@ -45,13 +45,7 @@ import org.kohsuke.accmod.restrictions.Beta;
|
|||
* @since 2.480
|
||||
*/
|
||||
@Restricted(Beta.class)
|
||||
public interface IComputer extends AccessControlled, IconSpec {
|
||||
/**
|
||||
* Returns {@link Node#getNodeName() the name of the node}.
|
||||
*/
|
||||
@NonNull
|
||||
String getName();
|
||||
|
||||
public interface IComputer extends AccessControlled, IconSpec, ModelObject, Named {
|
||||
/**
|
||||
* Used to render the list of executors.
|
||||
* @return a snapshot of the executor display information
|
||||
|
@ -60,16 +54,14 @@ public interface IComputer extends AccessControlled, IconSpec {
|
|||
List<? extends IDisplayExecutor> getDisplayExecutors();
|
||||
|
||||
/**
|
||||
* @return {@code true} if the node is offline. {@code false} if it is online.
|
||||
* Returns whether the agent is offline for scheduling new tasks.
|
||||
* Even if {@code true}, the agent may still be connected to the controller and executing a task,
|
||||
* but is considered offline for scheduling.
|
||||
* @return {@code true} if the agent is offline; {@code false} if online.
|
||||
* @see #isConnected()
|
||||
*/
|
||||
boolean isOffline();
|
||||
|
||||
/**
|
||||
* @return the node name for UI purposes.
|
||||
*/
|
||||
@NonNull
|
||||
String getDisplayName();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the computer is accepting tasks. Needed to allow agents programmatic suspension of task
|
||||
* scheduling that does not overlap with being offline.
|
||||
|
@ -192,10 +184,21 @@ public interface IComputer extends AccessControlled, IconSpec {
|
|||
int countExecutors();
|
||||
|
||||
/**
|
||||
* @return true if the computer is online.
|
||||
* Indicates whether the agent can accept a new task when it becomes idle.
|
||||
* {@code false} does not necessarily mean the agent is disconnected.
|
||||
* @return {@code true} if the agent is online.
|
||||
* @see #isConnected()
|
||||
*/
|
||||
boolean isOnline();
|
||||
|
||||
/**
|
||||
* Indicates whether the agent is actually connected to the controller.
|
||||
* @return {@code true} if the agent is connected to the controller.
|
||||
*/
|
||||
default boolean isConnected() {
|
||||
return isOnline();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of {@link IExecutor}s that are idle right now.
|
||||
*/
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
package jenkins.model;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import hudson.model.ModelObject;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.Beta;
|
||||
|
||||
|
@ -34,13 +35,7 @@ import org.kohsuke.accmod.restrictions.Beta;
|
|||
* @since 2.480
|
||||
*/
|
||||
@Restricted(Beta.class)
|
||||
public interface IDisplayExecutor {
|
||||
/**
|
||||
* @return The UI label for this executor.
|
||||
*/
|
||||
@NonNull
|
||||
String getDisplayName();
|
||||
|
||||
public interface IDisplayExecutor extends ModelObject {
|
||||
/**
|
||||
* @return the URL where to reach specifically this executor, relative to Jenkins URL.
|
||||
*/
|
||||
|
|
|
@ -1731,6 +1731,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
|
|||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return "";
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package jenkins.model;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
|
||||
/**
|
||||
* An object that has a name.
|
||||
* <p>
|
||||
* This interface is used to provide a consistent way to retrieve the name of an object in Jenkins.
|
||||
* It is typically implemented by objects that need to be identified by a name, such as tasks, nodes, or other model objects.
|
||||
*/
|
||||
public interface Named {
|
||||
/**
|
||||
* Returns the name of this object.
|
||||
*
|
||||
* @return the name of this object, never null.
|
||||
*/
|
||||
@NonNull
|
||||
String getName();
|
||||
}
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
package jenkins.model;
|
||||
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_CONFLICT;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_CREATED;
|
||||
|
||||
|
@ -201,7 +202,7 @@ public abstract class ParameterizedJobMixIn<JobT extends Job<JobT, RunT> & Param
|
|||
}
|
||||
|
||||
if (!asJob().isBuildable()) {
|
||||
throw HttpResponses.error(SC_CONFLICT, new IOException(asJob().getFullName() + " is not buildable"));
|
||||
throw HttpResponses.errorWithoutStack(SC_CONFLICT, asJob().getFullName() + " is not buildable");
|
||||
}
|
||||
|
||||
// if a build is parameterized, let that take over
|
||||
|
@ -238,12 +239,12 @@ public abstract class ParameterizedJobMixIn<JobT extends Job<JobT, RunT> & Param
|
|||
|
||||
ParametersDefinitionProperty pp = asJob().getProperty(ParametersDefinitionProperty.class);
|
||||
if (!asJob().isBuildable()) {
|
||||
throw HttpResponses.error(SC_CONFLICT, new IOException(asJob().getFullName() + " is not buildable!"));
|
||||
throw HttpResponses.errorWithoutStack(SC_CONFLICT, asJob().getFullName() + " is not buildable");
|
||||
}
|
||||
if (pp != null) {
|
||||
pp.buildWithParameters(req, rsp, delay);
|
||||
} else {
|
||||
throw new IllegalStateException("This build is not parameterized!");
|
||||
throw HttpResponses.errorWithoutStack(SC_BAD_REQUEST, asJob().getFullName() + " is not parameterized");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,30 +26,26 @@ package jenkins.model.lazy;
|
|||
|
||||
import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.ASC;
|
||||
import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.DESC;
|
||||
import static jenkins.model.lazy.AbstractLazyLoadRunMap.Direction.EXACT;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
import hudson.model.Job;
|
||||
import hudson.model.Run;
|
||||
import hudson.model.RunMap;
|
||||
import hudson.model.listeners.RunListener;
|
||||
import hudson.util.CopyOnWriteMap;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.AbstractCollection;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.IntPredicate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
@ -87,203 +83,44 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
|
|||
*
|
||||
* <p>
|
||||
* Some of the {@link SortedMap} operations are inefficiently implemented, by
|
||||
* {@linkplain #all() loading all the build records eagerly}. We hope to replace
|
||||
* loading all the build records eagerly. We hope to replace
|
||||
* these implementations by more efficient lazy-loading ones as we go.
|
||||
*
|
||||
* <p>
|
||||
* Object lock of {@code this} is used to make sure mutation occurs sequentially.
|
||||
* That is, ensure that only one thread is actually calling {@link #retrieve(File)} and
|
||||
* updating {@link jenkins.model.lazy.AbstractLazyLoadRunMap.Index#byNumber}.
|
||||
* updating {@link jenkins.model.lazy.AbstractLazyLoadRunMap#core}.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @since 1.485
|
||||
*/
|
||||
public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R> implements SortedMap<Integer, R> {
|
||||
/**
|
||||
* Used in {@link #all()} to quickly determine if we've already loaded everything.
|
||||
*/
|
||||
private volatile boolean fullyLoaded;
|
||||
|
||||
/**
|
||||
* Currently visible index.
|
||||
* Updated atomically. Once set to this field, the index object may not be modified.
|
||||
*/
|
||||
private volatile Index index = new Index();
|
||||
private LazyLoadRunMapEntrySet<R> entrySet = new LazyLoadRunMapEntrySet<>(this);
|
||||
|
||||
private transient volatile Set<Integer> keySet;
|
||||
private transient volatile Collection<R> values;
|
||||
private final CopyOnWriteMap.Tree<Integer, BuildReference<R>> core = new CopyOnWriteMap.Tree<>(
|
||||
Collections.reverseOrder());
|
||||
private final BuildReferenceMapAdapter.Resolver<R> buildResolver = new BuildReferenceMapAdapterResolver();
|
||||
private final BuildReferenceMapAdapter<R> adapter = new BuildReferenceMapAdapter<>(core, buildResolver) {
|
||||
@Override
|
||||
protected boolean removeValue(R value) {
|
||||
return AbstractLazyLoadRunMap.this.removeValue(value);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Set<Integer> keySet() {
|
||||
Set<Integer> ks = keySet;
|
||||
if (ks == null) {
|
||||
ks = new AbstractSet<>() {
|
||||
@Override
|
||||
public Iterator<Integer> iterator() {
|
||||
return new Iterator() {
|
||||
private final Iterator<Entry<Integer, R>> it = entrySet().iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer next() {
|
||||
return it.next().getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
it.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<Integer> spliterator() {
|
||||
return new Spliterators.AbstractIntSpliterator(
|
||||
Long.MAX_VALUE,
|
||||
Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.SORTED) {
|
||||
private final Iterator<Integer> it = iterator();
|
||||
|
||||
@Override
|
||||
public boolean tryAdvance(IntConsumer action) {
|
||||
if (action == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
action.accept(it.next());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<Integer> getComparator() {
|
||||
return Collections.reverseOrder();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return AbstractLazyLoadRunMap.this.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return AbstractLazyLoadRunMap.this.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
AbstractLazyLoadRunMap.this.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object k) {
|
||||
return AbstractLazyLoadRunMap.this.containsKey(k);
|
||||
}
|
||||
};
|
||||
keySet = ks;
|
||||
}
|
||||
return ks;
|
||||
return adapter.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<R> values() {
|
||||
Collection<R> vals = values;
|
||||
if (vals == null) {
|
||||
vals = new AbstractCollection<>() {
|
||||
@Override
|
||||
public Iterator<R> iterator() {
|
||||
return new Iterator<>() {
|
||||
private final Iterator<Entry<Integer, R>> it = entrySet().iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public R next() {
|
||||
return it.next().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
it.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<R> spliterator() {
|
||||
return Spliterators.spliteratorUnknownSize(
|
||||
iterator(), Spliterator.DISTINCT | Spliterator.ORDERED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return AbstractLazyLoadRunMap.this.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return AbstractLazyLoadRunMap.this.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
AbstractLazyLoadRunMap.this.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object v) {
|
||||
return AbstractLazyLoadRunMap.this.containsValue(v);
|
||||
}
|
||||
};
|
||||
values = vals;
|
||||
}
|
||||
return vals;
|
||||
return adapter.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Historical holder for map.
|
||||
*
|
||||
* TODO all this mess including {@link #numberOnDisk} could probably be simplified to a single {@code TreeMap<Integer,BuildReference<R>>}
|
||||
* where a null value means not yet loaded and a broken entry just uses {@code NoHolder}.
|
||||
*
|
||||
* The idiom is that you put yourself in a synchronized block, {@linkplain #copy() make a copy of this},
|
||||
* update the copy, then set it to {@link #index}.
|
||||
*/
|
||||
private class Index {
|
||||
/**
|
||||
* Stores the mapping from build number to build, for builds that are already loaded.
|
||||
*
|
||||
* If we have known load failure of the given ID, we record that in the map
|
||||
* by using the null value (not to be confused with a non-null {@link BuildReference}
|
||||
* with null referent, which just means the record was GCed.)
|
||||
*/
|
||||
private final TreeMap<Integer, BuildReference<R>> byNumber;
|
||||
|
||||
private Index() {
|
||||
byNumber = new TreeMap<>(Collections.reverseOrder());
|
||||
}
|
||||
|
||||
private Index(Index rhs) {
|
||||
byNumber = new TreeMap<>(rhs.byNumber);
|
||||
}
|
||||
@Override
|
||||
public Set<Entry<Integer, R>> entrySet() {
|
||||
assert baseDirInitialized();
|
||||
return adapter.entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build numbers found on disk, in the ascending order.
|
||||
*/
|
||||
// copy on write
|
||||
private volatile SortedIntList numberOnDisk = new SortedIntList(0);
|
||||
|
||||
/**
|
||||
* Base directory for data.
|
||||
* In effect this is treated as a final field, but can't mark it final
|
||||
|
@ -330,8 +167,6 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
* @since 1.507
|
||||
*/
|
||||
public synchronized void purgeCache() {
|
||||
index = new Index();
|
||||
fullyLoaded = false;
|
||||
loadNumberOnDisk();
|
||||
}
|
||||
|
||||
|
@ -343,7 +178,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
// the job may have just been created
|
||||
kids = MemoryReductionUtil.EMPTY_STRING_ARRAY;
|
||||
}
|
||||
SortedIntList list = new SortedIntList(kids.length / 2);
|
||||
TreeMap<Integer, BuildReference<R>> newBuildRefsMap = new TreeMap<>();
|
||||
var allower = createLoadAllower();
|
||||
for (String s : kids) {
|
||||
if (!BUILD_NUMBER.matcher(s).matches()) {
|
||||
|
@ -353,7 +188,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
try {
|
||||
int buildNumber = Integer.parseInt(s);
|
||||
if (allower.test(buildNumber)) {
|
||||
list.add(buildNumber);
|
||||
newBuildRefsMap.put(buildNumber, new BuildReference<>(s));
|
||||
} else {
|
||||
LOGGER.fine(() -> "declining to consider " + buildNumber + " in " + dir);
|
||||
}
|
||||
|
@ -361,8 +196,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
// matched BUILD_NUMBER but not an int?
|
||||
}
|
||||
}
|
||||
list.sort();
|
||||
numberOnDisk = list;
|
||||
core.replaceBy(newBuildRefsMap);
|
||||
}
|
||||
|
||||
@Restricted(NoExternalUse.class)
|
||||
|
@ -384,13 +218,10 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
public final void recognizeNumber(int buildNumber) {
|
||||
if (new File(dir, Integer.toString(buildNumber)).isDirectory()) {
|
||||
synchronized (this) {
|
||||
SortedIntList list = new SortedIntList(numberOnDisk);
|
||||
if (list.contains(buildNumber)) {
|
||||
if (this.core.containsKey(buildNumber)) {
|
||||
LOGGER.fine(() -> "already knew about " + buildNumber + " in " + dir);
|
||||
} else {
|
||||
list.add(buildNumber);
|
||||
list.sort();
|
||||
numberOnDisk = list;
|
||||
core.put(buildNumber, new BuildReference<>(String.valueOf(buildNumber)));
|
||||
LOGGER.fine(() -> "recognizing " + buildNumber + " in " + dir);
|
||||
}
|
||||
}
|
||||
|
@ -401,25 +232,36 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
|
||||
@Override
|
||||
public Comparator<? super Integer> comparator() {
|
||||
return Collections.reverseOrder();
|
||||
return core.comparator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return search(Integer.MAX_VALUE, DESC) == null;
|
||||
return adapter.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Integer, R>> entrySet() {
|
||||
assert baseDirInitialized();
|
||||
return entrySet;
|
||||
public boolean containsKey(Object value) {
|
||||
return adapter.containsKey(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return adapter.containsValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a read-only view of records that has already been loaded.
|
||||
*/
|
||||
public SortedMap<Integer, R> getLoadedBuilds() {
|
||||
return Collections.unmodifiableSortedMap(new BuildReferenceMapAdapter<>(this, index.byNumber));
|
||||
TreeMap<Integer, BuildReference<R>> res = new TreeMap<>(Comparator.reverseOrder());
|
||||
for (var entry : this.core.entrySet()) {
|
||||
BuildReference<R> buildRef = entry.getValue();
|
||||
if (buildRef.isSet() && !buildRef.isUnloadable()) {
|
||||
res.put(entry.getKey(), buildRef);
|
||||
}
|
||||
}
|
||||
return new BuildReferenceMapAdapter<>(res, buildResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -430,47 +272,27 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
*/
|
||||
@Override
|
||||
public SortedMap<Integer, R> subMap(Integer fromKey, Integer toKey) {
|
||||
// TODO: if this method can produce a lazy map, that'd be wonderful
|
||||
// because due to the lack of floor/ceil/higher/lower kind of methods
|
||||
// to look up keys in SortedMap, various places of Jenkins rely on
|
||||
// subMap+firstKey/lastKey combo.
|
||||
|
||||
R start = search(fromKey, DESC);
|
||||
if (start == null) return EMPTY_SORTED_MAP;
|
||||
|
||||
R end = search(toKey, ASC);
|
||||
if (end == null) return EMPTY_SORTED_MAP;
|
||||
|
||||
for (R i = start; i != end; ) {
|
||||
i = search(getNumberOf(i) - 1, DESC);
|
||||
assert i != null;
|
||||
}
|
||||
|
||||
return Collections.unmodifiableSortedMap(new BuildReferenceMapAdapter<>(this, index.byNumber.subMap(fromKey, toKey)));
|
||||
return adapter.subMap(fromKey, toKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedMap<Integer, R> headMap(Integer toKey) {
|
||||
return subMap(Integer.MAX_VALUE, toKey);
|
||||
return adapter.headMap(toKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedMap<Integer, R> tailMap(Integer fromKey) {
|
||||
return subMap(fromKey, Integer.MIN_VALUE);
|
||||
return adapter.tailMap(fromKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer firstKey() {
|
||||
R r = newestBuild();
|
||||
if (r == null) throw new NoSuchElementException();
|
||||
return getNumberOf(r);
|
||||
return adapter.firstKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer lastKey() {
|
||||
R r = oldestBuild();
|
||||
if (r == null) throw new NoSuchElementException();
|
||||
return getNumberOf(r);
|
||||
return adapter.lastKey();
|
||||
}
|
||||
|
||||
public R newestBuild() {
|
||||
|
@ -483,11 +305,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
|
||||
@Override
|
||||
public R get(Object key) {
|
||||
if (key instanceof Integer) {
|
||||
int n = (Integer) key;
|
||||
return get(n);
|
||||
}
|
||||
return super.get(key);
|
||||
return adapter.get(key);
|
||||
}
|
||||
|
||||
public R get(int n) {
|
||||
|
@ -503,7 +321,7 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
* @since 2.14
|
||||
*/
|
||||
public boolean runExists(int number) {
|
||||
return numberOnDisk.contains(number);
|
||||
return this.core.containsKey(number);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -518,72 +336,51 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
* If DESC, finds the closest #M that satisfies M ≤ N.
|
||||
*/
|
||||
public @CheckForNull R search(final int n, final Direction d) {
|
||||
switch (d) {
|
||||
case EXACT:
|
||||
return getByNumber(n);
|
||||
case ASC:
|
||||
for (int m : numberOnDisk) {
|
||||
if (m < n) {
|
||||
// TODO could be made more efficient with numberOnDisk.find
|
||||
continue;
|
||||
}
|
||||
R r = getByNumber(m);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case DESC:
|
||||
// TODO again could be made more efficient
|
||||
ListIterator<Integer> iterator = numberOnDisk.listIterator(numberOnDisk.size());
|
||||
while (iterator.hasPrevious()) {
|
||||
int m = iterator.previous();
|
||||
if (m > n) {
|
||||
continue;
|
||||
}
|
||||
R r = getByNumber(m);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
if (d == EXACT) {
|
||||
return this.adapter.get(n);
|
||||
}
|
||||
// prepare sub map, where we need to find first resolvable entry
|
||||
NavigableMap<Integer, BuildReference<R>> subCore = (d == ASC)
|
||||
? core.headMap(n, true).descendingMap()
|
||||
: core.tailMap(n, true);
|
||||
// wrap with BuildReferenceMapAdapter to skip unresolvable entries
|
||||
return new BuildReferenceMapAdapter<>(subCore, buildResolver).values().stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public R getById(String id) {
|
||||
return getByNumber(Integer.parseInt(id));
|
||||
}
|
||||
|
||||
public R getByNumber(int n) {
|
||||
Index snapshot = index;
|
||||
if (snapshot.byNumber.containsKey(n)) {
|
||||
BuildReference<R> ref = snapshot.byNumber.get(n);
|
||||
if (ref == null) {
|
||||
LOGGER.fine(() -> "known failure of #" + n + " in " + dir);
|
||||
return null;
|
||||
}
|
||||
R v = unwrap(ref);
|
||||
if (v != null) {
|
||||
/**
|
||||
* Ensure loading referent object if needed, cache it and return
|
||||
* Save that object as 'unloadable' in case of failure to avoid next load attempts
|
||||
*
|
||||
* @param ref reference object to be resolved
|
||||
* @return R referent build object, or null if it can't be resolved
|
||||
*/
|
||||
private R resolveBuildRef(BuildReference<R> ref) {
|
||||
if (ref == null || ref.isUnloadable()) {
|
||||
return null;
|
||||
}
|
||||
R v;
|
||||
if ((v = ref.get()) != null) {
|
||||
return v; // already in memory
|
||||
}
|
||||
// otherwise fall through to load
|
||||
synchronized (this) {
|
||||
if ((v = ref.get()) != null) {
|
||||
return v; // already in memory
|
||||
}
|
||||
// otherwise fall through to load
|
||||
}
|
||||
synchronized (this) {
|
||||
if (index.byNumber.containsKey(n)) { // JENKINS-22767: recheck inside lock
|
||||
BuildReference<R> ref = index.byNumber.get(n);
|
||||
if (ref == null) {
|
||||
LOGGER.fine(() -> "known failure of #" + n + " in " + dir);
|
||||
int n = ref.number;
|
||||
if (allowLoad(n)) {
|
||||
v = load(n);
|
||||
// save if build unloadable
|
||||
if (v == null) {
|
||||
ref.setUnloadable();
|
||||
return null;
|
||||
}
|
||||
R v = unwrap(ref);
|
||||
if (v != null) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
if (allowLoad(n)) {
|
||||
return load(n, null);
|
||||
ref.set(v);
|
||||
return v;
|
||||
} else {
|
||||
LOGGER.fine(() -> "declining to load " + n + " in " + dir);
|
||||
return null;
|
||||
|
@ -591,17 +388,25 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
}
|
||||
}
|
||||
|
||||
public R getByNumber(int n) {
|
||||
return adapter.get(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the highest recorded build number, or 0 if there are none
|
||||
*/
|
||||
@Restricted(NoExternalUse.class)
|
||||
public synchronized int maxNumberOnDisk() {
|
||||
return numberOnDisk.max();
|
||||
try {
|
||||
return this.core.firstKey();
|
||||
} catch (NoSuchElementException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected final synchronized void proposeNewNumber(int number) throws IllegalStateException {
|
||||
if (number <= maxNumberOnDisk()) {
|
||||
throw new IllegalStateException("JENKINS-27530: cannot create a build with number " + number + " since that (or higher) is already in use among " + numberOnDisk);
|
||||
throw new IllegalStateException("JENKINS-27530: cannot create a build with number " + number + " since that (or higher) is already in use among " + keySet());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -616,95 +421,36 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
@Override
|
||||
public synchronized R put(Integer key, R r) {
|
||||
int n = getNumberOf(r);
|
||||
|
||||
Index copy = copy();
|
||||
BuildReference<R> ref = createReference(r);
|
||||
BuildReference<R> old = copy.byNumber.put(n, ref);
|
||||
index = copy;
|
||||
|
||||
if (!numberOnDisk.contains(n)) {
|
||||
SortedIntList a = new SortedIntList(numberOnDisk);
|
||||
a.add(n);
|
||||
a.sort();
|
||||
numberOnDisk = a;
|
||||
}
|
||||
|
||||
entrySet.clearCache();
|
||||
|
||||
return unwrap(old);
|
||||
}
|
||||
|
||||
private R unwrap(BuildReference<R> ref) {
|
||||
return ref != null ? ref.get() : null;
|
||||
BuildReference<R> old = core.put(n, createReference(r));
|
||||
return resolveBuildRef(old);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void putAll(Map<? extends Integer, ? extends R> rhs) {
|
||||
Index copy = copy();
|
||||
for (R r : rhs.values()) {
|
||||
BuildReference<R> ref = createReference(r);
|
||||
copy.byNumber.put(getNumberOf(r), ref);
|
||||
public synchronized void putAll(Map<? extends Integer, ? extends R> newData) {
|
||||
TreeMap<Integer, BuildReference<R>> newWrapperData = new TreeMap<>();
|
||||
for (Map.Entry<? extends Integer, ? extends R> entry : newData.entrySet()) {
|
||||
newWrapperData.put(entry.getKey(), createReference(entry.getValue()));
|
||||
}
|
||||
index = copy;
|
||||
core.putAll(newWrapperData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all the build records to fully populate the map.
|
||||
* Calling this method results in eager loading everything,
|
||||
* so the whole point of this class is to avoid this call as much as possible
|
||||
* for typical code path.
|
||||
*
|
||||
* @return
|
||||
* fully populated map.
|
||||
*/
|
||||
/*package*/ TreeMap<Integer, BuildReference<R>> all() {
|
||||
if (!fullyLoaded) {
|
||||
synchronized (this) {
|
||||
if (!fullyLoaded) {
|
||||
Index copy = copy();
|
||||
for (Integer number : numberOnDisk) {
|
||||
if (!copy.byNumber.containsKey(number))
|
||||
load(number, copy);
|
||||
}
|
||||
index = copy;
|
||||
fullyLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return index.byNumber;
|
||||
@Override
|
||||
public R remove(Object key) {
|
||||
return adapter.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a duplicate for the COW data structure in preparation for mutation.
|
||||
*/
|
||||
private Index copy() {
|
||||
return new Index(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to load the record #N.
|
||||
*
|
||||
* @return null if the data failed to load.
|
||||
*/
|
||||
private R load(int n, Index editInPlace) {
|
||||
private R load(int n) {
|
||||
assert Thread.holdsLock(this);
|
||||
assert dir != null;
|
||||
R v = load(new File(dir, String.valueOf(n)), editInPlace);
|
||||
if (v == null && editInPlace != null) {
|
||||
// remember the failure.
|
||||
// if editInPlace==null, we can create a new copy for this, but not sure if it's worth doing,
|
||||
// TODO should we also update numberOnDisk?
|
||||
editInPlace.byNumber.put(n, null);
|
||||
}
|
||||
return v;
|
||||
return load(new File(dir, String.valueOf(n)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param editInPlace
|
||||
* If non-null, update this data structure.
|
||||
* Otherwise do a copy-on-write of {@link #index}
|
||||
*/
|
||||
private R load(File dataDir, Index editInPlace) {
|
||||
private R load(File dataDir) {
|
||||
assert Thread.holdsLock(this);
|
||||
try {
|
||||
R r = retrieve(dataDir);
|
||||
|
@ -712,15 +458,6 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
LOGGER.fine(() -> "nothing in " + dataDir);
|
||||
return null;
|
||||
}
|
||||
|
||||
Index copy = editInPlace != null ? editInPlace : new Index(index);
|
||||
|
||||
BuildReference<R> ref = createReference(r);
|
||||
BuildReference<R> old = copy.byNumber.put(getNumberOf(r), ref);
|
||||
assert old == null || old.get() == null : "tried to overwrite " + old + " with " + ref;
|
||||
|
||||
if (editInPlace == null) index = copy;
|
||||
|
||||
return r;
|
||||
} catch (IOException e) {
|
||||
LOGGER.log(Level.WARNING, "Failed to load " + dataDir, e);
|
||||
|
@ -747,7 +484,6 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
return new BuildReference<>(getIdOf(r), r);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses {@code R} instance from data in the specified directory.
|
||||
*
|
||||
|
@ -759,31 +495,22 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
*/
|
||||
protected abstract R retrieve(File dir) throws IOException;
|
||||
|
||||
protected abstract Class<R> getBuildClass();
|
||||
|
||||
public synchronized boolean removeValue(R run) {
|
||||
Index copy = copy();
|
||||
int n = getNumberOf(run);
|
||||
BuildReference<R> old = copy.byNumber.remove(n);
|
||||
SortedIntList a = new SortedIntList(numberOnDisk);
|
||||
a.removeValue(n);
|
||||
numberOnDisk = a;
|
||||
this.index = copy;
|
||||
|
||||
entrySet.clearCache();
|
||||
|
||||
return old != null;
|
||||
return core.remove(getNumberOf(run)) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all the current loaded Rs with the given ones.
|
||||
*/
|
||||
public synchronized void reset(TreeMap<Integer, R> builds) {
|
||||
Index index = new Index();
|
||||
public synchronized void reset(Map<Integer, R> builds) {
|
||||
TreeMap<Integer, BuildReference<R>> copy = new TreeMap<>();
|
||||
for (R r : builds.values()) {
|
||||
BuildReference<R> ref = createReference(r);
|
||||
index.byNumber.put(getNumberOf(r), ref);
|
||||
copy.put(getNumberOf(r), createReference(r));
|
||||
}
|
||||
|
||||
this.index = index;
|
||||
this.core.replaceBy(copy);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -800,7 +527,22 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer, R>
|
|||
ASC, DESC, EXACT
|
||||
}
|
||||
|
||||
private static final SortedMap EMPTY_SORTED_MAP = Collections.unmodifiableSortedMap(new TreeMap());
|
||||
private class BuildReferenceMapAdapterResolver implements BuildReferenceMapAdapter.Resolver<R> {
|
||||
@Override
|
||||
public R resolveBuildRef(BuildReference<R> buildRef) {
|
||||
return AbstractLazyLoadRunMap.this.resolveBuildRef(buildRef);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getNumberOf(R build) {
|
||||
return AbstractLazyLoadRunMap.this.getNumberOf(build);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<R> getBuildClass() {
|
||||
return AbstractLazyLoadRunMap.this.getBuildClass();
|
||||
}
|
||||
}
|
||||
|
||||
static final Logger LOGGER = Logger.getLogger(AbstractLazyLoadRunMap.class.getName());
|
||||
}
|
||||
|
|
|
@ -36,13 +36,57 @@ public final class BuildReference<R> {
|
|||
private static final Logger LOGGER = Logger.getLogger(BuildReference.class.getName());
|
||||
|
||||
final String id;
|
||||
final int number;
|
||||
private volatile Holder<R> holder;
|
||||
|
||||
public BuildReference(String id, R referent) {
|
||||
public BuildReference(String id) {
|
||||
this.id = id;
|
||||
this.holder = findHolder(referent);
|
||||
int num;
|
||||
try {
|
||||
num = Integer.parseInt(id);
|
||||
} catch (NumberFormatException ignored) {
|
||||
num = Integer.MAX_VALUE;
|
||||
}
|
||||
this.number = num;
|
||||
}
|
||||
|
||||
public BuildReference(String id, R referent) {
|
||||
this(id);
|
||||
set(referent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set referent if loaded
|
||||
*/
|
||||
/*package*/ void set(R referent) {
|
||||
holder = findHolder(referent);
|
||||
}
|
||||
|
||||
/**
|
||||
* check if reference marked as unloadable
|
||||
*/
|
||||
/*package*/ boolean isUnloadable() {
|
||||
return DefaultHolderFactory.UnloadableHolder.getInstance() == holder;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if reference holder set.
|
||||
* means there war a try to load build object and we have some result of that try
|
||||
*
|
||||
* @return true if there was a try to
|
||||
*/
|
||||
/*package*/ boolean isSet() {
|
||||
return holder != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set referent as unloadable
|
||||
*/
|
||||
/*package*/ void setUnloadable() {
|
||||
holder = DefaultHolderFactory.UnloadableHolder.getInstance();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the build if still in memory.
|
||||
* @return the actual build, or null if it has been collected
|
||||
|
@ -155,7 +199,7 @@ public final class BuildReference<R> {
|
|||
} else if (mode.equals("strong")) {
|
||||
return new StrongHolder<>(referent);
|
||||
} else if (mode.equals("none")) {
|
||||
return new NoHolder<>();
|
||||
return NoHolder.getInstance();
|
||||
} else {
|
||||
throw new IllegalStateException("unrecognized value of " + MODE_PROPERTY + ": " + mode);
|
||||
}
|
||||
|
@ -186,6 +230,32 @@ public final class BuildReference<R> {
|
|||
}
|
||||
|
||||
private static final class NoHolder<R> implements Holder<R> {
|
||||
static final NoHolder<?> INSTANCE = new NoHolder<>();
|
||||
|
||||
static <R> NoHolder<R> getInstance() {
|
||||
//noinspection unchecked
|
||||
return (NoHolder<R>) INSTANCE;
|
||||
}
|
||||
|
||||
private NoHolder() {
|
||||
}
|
||||
|
||||
@Override public R get() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UnloadableHolder<R> implements Holder<R> {
|
||||
static final UnloadableHolder<?> INSTANCE = new UnloadableHolder<>();
|
||||
|
||||
static <R> UnloadableHolder<R> getInstance() {
|
||||
//noinspection unchecked
|
||||
return (UnloadableHolder<R>) INSTANCE;
|
||||
}
|
||||
|
||||
private UnloadableHolder() {
|
||||
}
|
||||
|
||||
@Override public R get() {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,54 +1,61 @@
|
|||
package jenkins.model.lazy;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.Nullable;
|
||||
import hudson.util.AdaptedIterator;
|
||||
import hudson.util.Iterators;
|
||||
import java.util.AbstractCollection;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
* Take {@code SortedMap<Integer,BuildReference<R>>} and make it look like {@code SortedMap<Integer,R>}.
|
||||
*
|
||||
* When {@link BuildReference} lost the build object, we'll use {@link AbstractLazyLoadRunMap#getById(String)}
|
||||
* to obtain one.
|
||||
* <p>
|
||||
* When {@link BuildReference} lost the build object, we'll use {@link Resolver} to obtain one.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* By default, this adapter provides a read-only view of the underlying {@link #core} map,
|
||||
* which may be modified externally to change the adapter's state.
|
||||
* Support for removal operations through {@link #entrySet()}, {@link #values()},
|
||||
* {@link #keySet()}, and their iterators can be enabled by overriding
|
||||
* {@link #removeValue(Object)}. This method is invoked by the internal collection views
|
||||
* to perform the actual removal logic (such as updating {@link #core} or performing other actions).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Some operations are weakly implemented (for example, {@link #size()} may be approximate).
|
||||
* This adapter implements {@link SortedMap}, which does not allow {@code null} keys; however,
|
||||
* methods such as {@code get(null)} or {@code containsKey(null)} do not throw a {@link NullPointerException}
|
||||
* and instead return {@code null} or {@code false}, respectively, indicating that the key was not found.
|
||||
* </p>
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class BuildReferenceMapAdapter<R> implements SortedMap<Integer, R> {
|
||||
private final AbstractLazyLoadRunMap<R> loader;
|
||||
class BuildReferenceMapAdapter<R> extends AbstractMap<Integer, R> implements SortedMap<Integer, R> {
|
||||
private final NavigableMap<Integer, BuildReference<R>> core;
|
||||
private final Resolver<R> resolver;
|
||||
|
||||
private final SortedMap<Integer, BuildReference<R>> core;
|
||||
private final Set<Integer> keySet = new KeySetAdapter();
|
||||
private final Collection<R> values = new ValuesAdapter();
|
||||
private final Set<Map.Entry<Integer, R>> entrySet = new EntrySetAdapter();
|
||||
|
||||
BuildReferenceMapAdapter(AbstractLazyLoadRunMap<R> loader, SortedMap<Integer, BuildReference<R>> core) {
|
||||
this.loader = loader;
|
||||
BuildReferenceMapAdapter(NavigableMap<Integer, BuildReference<R>> core, Resolver<R> resolver) {
|
||||
this.core = core;
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
private R unwrap(@Nullable BuildReference<R> ref) {
|
||||
if (ref == null) return null;
|
||||
|
||||
R v = ref.get();
|
||||
if (v == null)
|
||||
v = loader.getById(ref.id);
|
||||
return v;
|
||||
}
|
||||
|
||||
private BuildReference<R> wrap(@Nullable R value) {
|
||||
if (value == null) return null;
|
||||
return loader.createReference(value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Comparator<? super Integer> comparator() {
|
||||
return core.comparator();
|
||||
|
@ -56,330 +63,274 @@ class BuildReferenceMapAdapter<R> implements SortedMap<Integer, R> {
|
|||
|
||||
@Override
|
||||
public SortedMap<Integer, R> subMap(Integer fromKey, Integer toKey) {
|
||||
return new BuildReferenceMapAdapter<>(loader, core.subMap(fromKey, toKey));
|
||||
return new BuildReferenceMapAdapter<>(core.subMap(fromKey, true, toKey, false), resolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedMap<Integer, R> headMap(Integer toKey) {
|
||||
return new BuildReferenceMapAdapter<>(loader, core.headMap(toKey));
|
||||
return new BuildReferenceMapAdapter<>(core.headMap(toKey, false), resolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SortedMap<Integer, R> tailMap(Integer fromKey) {
|
||||
return new BuildReferenceMapAdapter<>(loader, core.tailMap(fromKey));
|
||||
return new BuildReferenceMapAdapter<>(core.tailMap(fromKey, true), resolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer firstKey() {
|
||||
return core.firstKey();
|
||||
return keySet.stream().findFirst().orElseThrow(NoSuchElementException::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer lastKey() {
|
||||
return core.lastKey();
|
||||
return new BuildReferenceMapAdapter<>(core.descendingMap(), resolver).firstKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Integer> keySet() {
|
||||
return core.keySet();
|
||||
return keySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<R> values() {
|
||||
return new CollectionAdapter(core.values());
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Integer, R>> entrySet() {
|
||||
return new SetAdapter(core.entrySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return core.size();
|
||||
return entrySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return core.isEmpty();
|
||||
return entrySet().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return core.containsKey(key);
|
||||
BuildReference<R> ref = key instanceof Integer ? core.get(key) : null;
|
||||
if (ref == null) {
|
||||
return false;
|
||||
}
|
||||
// if found, check if value is loadable
|
||||
if (!ref.isSet()) {
|
||||
resolver.resolveBuildRef(ref);
|
||||
}
|
||||
return !ref.isUnloadable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return core.containsValue(value); // TODO should this be core.containsValue(wrap(value))?
|
||||
if (!resolver.getBuildClass().isInstance(value)) {
|
||||
return false;
|
||||
}
|
||||
R val = resolver.getBuildClass().cast(value);
|
||||
return val.equals(get(resolver.getNumberOf(val)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public R get(Object key) {
|
||||
return unwrap(core.get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public R put(Integer key, R value) {
|
||||
return unwrap(core.put(key, wrap(value)));
|
||||
return key instanceof Integer ? resolver.resolveBuildRef(core.get(key)) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public R remove(Object key) {
|
||||
return unwrap(core.remove(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends Integer, ? extends R> m) {
|
||||
for (Entry<? extends Integer, ? extends R> e : m.entrySet())
|
||||
put(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
core.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return core.equals(o); // TODO this is wrong
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return core.hashCode();
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return new LinkedHashMap<>(this).toString();
|
||||
}
|
||||
|
||||
private class CollectionAdapter implements Collection<R> {
|
||||
private final Collection<BuildReference<R>> core;
|
||||
|
||||
private CollectionAdapter(Collection<BuildReference<R>> core) {
|
||||
this.core = core;
|
||||
R val = get(key);
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
return removeValue(val) ? val : null;
|
||||
}
|
||||
|
||||
protected boolean removeValue(R value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private class KeySetAdapter extends AbstractSet<Integer> {
|
||||
@Override
|
||||
public int size() {
|
||||
return core.size();
|
||||
return BuildReferenceMapAdapter.this.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return core.isEmpty();
|
||||
return BuildReferenceMapAdapter.this.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
|
||||
throw new UnsupportedOperationException();
|
||||
public boolean contains(Object k) {
|
||||
return BuildReferenceMapAdapter.this.containsKey(k);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return BuildReferenceMapAdapter.this.remove(o) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Integer> iterator() {
|
||||
return new AdaptedIterator<>(BuildReferenceMapAdapter.this.entrySet().iterator()) {
|
||||
@Override
|
||||
protected Integer adapt(Entry<Integer, R> e) {
|
||||
return e.getKey();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<Integer> spliterator() {
|
||||
return new Spliterators.AbstractIntSpliterator(Long.MAX_VALUE,
|
||||
Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.SORTED) {
|
||||
private final Iterator<Integer> it = KeySetAdapter.this.iterator();
|
||||
|
||||
@Override
|
||||
public boolean tryAdvance(IntConsumer action) {
|
||||
Objects.requireNonNull(action);
|
||||
if (it.hasNext()) {
|
||||
action.accept(it.next());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<? super Integer> getComparator() {
|
||||
return BuildReferenceMapAdapter.this.comparator();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class ValuesAdapter extends AbstractCollection<R> {
|
||||
@Override
|
||||
public int size() {
|
||||
return BuildReferenceMapAdapter.this.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return BuildReferenceMapAdapter.this.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object v) {
|
||||
return BuildReferenceMapAdapter.this.containsValue(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return resolver.getBuildClass().isInstance(o) &&
|
||||
BuildReferenceMapAdapter.this.removeValue(resolver.getBuildClass().cast(o));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<R> iterator() {
|
||||
// silently drop null, as if we didn't have them in this collection in the first place
|
||||
// this shouldn't be indistinguishable from concurrent modifications to the collection
|
||||
return Iterators.removeNull(new AdaptedIterator<>(core.iterator()) {
|
||||
return new AdaptedIterator<>(BuildReferenceMapAdapter.this.entrySet().iterator()) {
|
||||
@Override
|
||||
protected R adapt(BuildReference<R> ref) {
|
||||
return unwrap(ref);
|
||||
protected R adapt(Entry<Integer, R> e) {
|
||||
return e.getValue();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
List<Object> list = new ArrayList<>(size());
|
||||
for (var e : this) {
|
||||
list.add(e);
|
||||
}
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return new ArrayList<>(this).toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(R value) {
|
||||
return core.add(wrap(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
// return core.remove(o);
|
||||
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
for (Object o : c) {
|
||||
if (!contains(o))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends R> c) {
|
||||
boolean b = false;
|
||||
for (R r : c) {
|
||||
b |= add(r);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
boolean b = false;
|
||||
for (Object o : c) {
|
||||
b |= remove(o);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
core.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return core.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return core.hashCode();
|
||||
public Spliterator<R> spliterator() {
|
||||
return Spliterators.spliteratorUnknownSize(iterator(), Spliterator.DISTINCT | Spliterator.ORDERED);
|
||||
}
|
||||
}
|
||||
|
||||
private class SetAdapter implements Set<Entry<Integer, R>> {
|
||||
private final Set<Entry<Integer, BuildReference<R>>> core;
|
||||
|
||||
private SetAdapter(Set<Entry<Integer, BuildReference<R>>> core) {
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
private class EntrySetAdapter extends AbstractSet<Entry<Integer, R>> {
|
||||
@Override
|
||||
public int size() {
|
||||
return core.size();
|
||||
return BuildReferenceMapAdapter.this.core.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return core.isEmpty();
|
||||
return this.stream().findFirst().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<Integer, R>> iterator() {
|
||||
return Iterators.removeNull(new AdaptedIterator<>(core.iterator()) {
|
||||
@Override
|
||||
protected Entry<Integer, R> adapt(Entry<Integer, BuildReference<R>> e) {
|
||||
return _unwrap(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
List<Object> list = new ArrayList<>(size());
|
||||
for (var e : this) {
|
||||
list.add(e);
|
||||
if (o instanceof Map.Entry<?, ?> e && e.getKey() instanceof Integer key) {
|
||||
return e.getValue() != null && e.getValue().equals(BuildReferenceMapAdapter.this.get(key));
|
||||
}
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return new ArrayList<>(this).toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Entry<Integer, R> value) {
|
||||
return core.add(_wrap(value));
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
// return core.remove(o);
|
||||
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
for (Object o : c) {
|
||||
if (!contains(o))
|
||||
return false;
|
||||
if (o instanceof Map.Entry<?, ?> e) {
|
||||
return resolver.getBuildClass().isInstance(e.getValue()) &&
|
||||
BuildReferenceMapAdapter.this.removeValue(resolver.getBuildClass().cast(e.getValue()));
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends Entry<Integer, R>> c) {
|
||||
boolean b = false;
|
||||
for (Entry<Integer, R> r : c) {
|
||||
b |= add(r);
|
||||
}
|
||||
return b;
|
||||
public Iterator<Entry<Integer, R>> iterator() {
|
||||
return new Iterator<>() {
|
||||
private Entry<Integer, R> current;
|
||||
private final Iterator<Entry<Integer, R>> it = Iterators.removeNull(Iterators.map(
|
||||
BuildReferenceMapAdapter.this.core.entrySet().iterator(), coreEntry -> {
|
||||
R v = BuildReferenceMapAdapter.this.resolver.resolveBuildRef(coreEntry.getValue());
|
||||
return v == null ? null : new AbstractMap.SimpleEntry<>(coreEntry.getKey(), v);
|
||||
}));
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Integer, R> next() {
|
||||
return current = it.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (current == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
BuildReferenceMapAdapter.this.removeValue(current.getValue());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
boolean b = false;
|
||||
for (Object o : c) {
|
||||
b |= remove(o);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
// TODO: to properly pass this onto core, we need to wrap o into BuildReference but also needs to figure out ID.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
core.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return core.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return core.hashCode();
|
||||
}
|
||||
|
||||
private Entry<Integer, BuildReference<R>> _wrap(Entry<Integer, R> e) {
|
||||
return new AbstractMap.SimpleEntry<>(e.getKey(), wrap(e.getValue()));
|
||||
}
|
||||
|
||||
private Entry<Integer, R> _unwrap(Entry<Integer, BuildReference<R>> e) {
|
||||
R v = unwrap(e.getValue());
|
||||
if (v == null)
|
||||
return null;
|
||||
return new AbstractMap.SimpleEntry<>(e.getKey(), v);
|
||||
public Spliterator<Map.Entry<Integer, R>> spliterator() {
|
||||
return Spliterators.spliteratorUnknownSize(iterator(), Spliterator.DISTINCT | Spliterator.ORDERED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for resolving build references into actual build instances
|
||||
* and extracting basic metadata from them.
|
||||
**/
|
||||
public interface Resolver<R> {
|
||||
|
||||
/**
|
||||
* Resolves the given build reference into an actual build instance.
|
||||
*
|
||||
* @param buildRef the reference to a build to resolve, can be {@code null}
|
||||
* @return the resolved build instance, or {@code null} if the reference is {@code null}
|
||||
* or could not be resolved
|
||||
*/
|
||||
R resolveBuildRef(BuildReference<R> buildRef);
|
||||
|
||||
/**
|
||||
* Returns the build number associated with the given build instance.
|
||||
*
|
||||
* @param build the build instance, cannot be null
|
||||
* @return the build number
|
||||
*/
|
||||
Integer getNumberOf(R build);
|
||||
|
||||
/**
|
||||
* Returns the class of the build type handled by this resolver.
|
||||
*
|
||||
* @return the {@link Class} of the build type {@code R}
|
||||
*/
|
||||
Class<R> getBuildClass();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ import java.nio.file.Files;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
|
@ -128,13 +129,15 @@ public abstract class LazyBuildMixIn<JobT extends Job<JobT, RunT> & Queue.Task &
|
|||
}
|
||||
if (currentBuilds != null) {
|
||||
// if we are reloading, keep all those that are still building intact
|
||||
TreeMap<Integer, RunT> stillBuildingBuilds = new TreeMap<>();
|
||||
for (RunT r : currentBuilds.getLoadedBuilds().values()) {
|
||||
if (r.isBuilding()) {
|
||||
// Do not use RunMap.put(Run):
|
||||
_builds.put(r.getNumber(), r);
|
||||
stillBuildingBuilds.put(r.getNumber(), r);
|
||||
LOGGER.log(Level.FINE, "keeping reloaded {0}", r);
|
||||
}
|
||||
}
|
||||
_builds.putAll(stillBuildingBuilds);
|
||||
}
|
||||
this.builds = _builds;
|
||||
}
|
||||
|
@ -145,6 +148,11 @@ public abstract class LazyBuildMixIn<JobT extends Job<JobT, RunT> & Queue.Task &
|
|||
public RunT create(File dir) throws IOException {
|
||||
return loadBuild(dir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<RunT> getBuildClass() {
|
||||
return LazyBuildMixIn.this.getBuildClass();
|
||||
}
|
||||
});
|
||||
return r;
|
||||
}
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
package jenkins.model.lazy;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
|
||||
|
||||
/**
|
||||
* Set that backs {@link AbstractLazyLoadRunMap#entrySet()}.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class LazyLoadRunMapEntrySet<R> extends AbstractSet<Map.Entry<Integer, R>> {
|
||||
private final AbstractLazyLoadRunMap<R> owner;
|
||||
|
||||
/**
|
||||
* Lazily loaded all entries.
|
||||
*/
|
||||
private Set<Map.Entry<Integer, R>> all;
|
||||
|
||||
LazyLoadRunMapEntrySet(AbstractLazyLoadRunMap<R> owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
private synchronized Set<Map.Entry<Integer, R>> all() {
|
||||
if (all == null)
|
||||
all = new BuildReferenceMapAdapter<>(owner, owner.all()).entrySet();
|
||||
return all;
|
||||
}
|
||||
|
||||
synchronized void clearCache() {
|
||||
all = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return all().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return owner.newestBuild() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
if (o instanceof Map.Entry) {
|
||||
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
|
||||
Object k = e.getKey();
|
||||
if (k instanceof Integer) {
|
||||
return owner.getByNumber((Integer) k).equals(e.getValue());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<Integer, R>> iterator() {
|
||||
return new Iterator<>() {
|
||||
R last = null;
|
||||
R next = owner.newestBuild();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return next != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<Integer, R> next() {
|
||||
last = next;
|
||||
if (last != null) {
|
||||
next = owner.search(owner.getNumberOf(last) - 1, Direction.DESC);
|
||||
} else
|
||||
throw new NoSuchElementException();
|
||||
return entryOf(last);
|
||||
}
|
||||
|
||||
private Map.Entry<Integer, R> entryOf(R r) {
|
||||
return new AbstractMap.SimpleImmutableEntry<>(owner.getNumberOf(r), r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (last == null)
|
||||
throw new UnsupportedOperationException();
|
||||
owner.removeValue(last);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<Map.Entry<Integer, R>> spliterator() {
|
||||
return Spliterators.spliteratorUnknownSize(
|
||||
iterator(), Spliterator.DISTINCT | Spliterator.ORDERED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return all().toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return all().toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Map.Entry<Integer, R> integerREntry) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
if (o instanceof Map.Entry) {
|
||||
Map.Entry e = (Map.Entry) o;
|
||||
return owner.removeValue((R) e.getValue());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2012, 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 jenkins.model.lazy;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* {@code ArrayList<Integer>} that uses {@code int} for storage.
|
||||
*
|
||||
* Plus a number of binary-search related methods that assume the array is sorted in the ascending order.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class SortedIntList extends AbstractList<Integer> {
|
||||
private int[] data;
|
||||
private int size;
|
||||
|
||||
SortedIntList(int capacity) {
|
||||
this.data = new int[capacity];
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal copy constructor.
|
||||
*/
|
||||
SortedIntList(SortedIntList that) {
|
||||
this.data = new int[that.size + 8];
|
||||
System.arraycopy(that.data, 0, this.data, 0,
|
||||
that.size);
|
||||
this.size = that.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search to find the position of the given string.
|
||||
*
|
||||
* @return
|
||||
* -(insertionPoint+1) if the exact string isn't found.
|
||||
* That is, -1 means the probe would be inserted at the very beginning.
|
||||
*/
|
||||
public int find(int probe) {
|
||||
return Arrays.binarySearch(data, 0, size, probe);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return o instanceof Integer && contains(((Integer) o).intValue());
|
||||
}
|
||||
|
||||
public boolean contains(int i) {
|
||||
return find(i) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer get(int index) {
|
||||
if (size <= index) throw new IndexOutOfBoundsException();
|
||||
return data[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public int max() {
|
||||
return size > 0 ? data[size - 1] : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Integer i) {
|
||||
return add(i.intValue());
|
||||
}
|
||||
|
||||
public boolean add(int i) {
|
||||
ensureCapacity(size + 1);
|
||||
data[size++] = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ensureCapacity(int i) {
|
||||
if (data.length < i) {
|
||||
int[] r = new int[Math.max(data.length * 2, i)];
|
||||
System.arraycopy(data, 0, r, 0, size);
|
||||
data = r;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the entry lower than v.
|
||||
*/
|
||||
public int lower(int v) {
|
||||
return Boundary.LOWER.apply(find(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the entry greater than v.
|
||||
*/
|
||||
public int higher(int v) {
|
||||
return Boundary.HIGHER.apply(find(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the entry lower or equal to v.
|
||||
*/
|
||||
public int floor(int v) {
|
||||
return Boundary.FLOOR.apply(find(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the entry greater or equal to v.
|
||||
*/
|
||||
public int ceil(int v) {
|
||||
return Boundary.CEIL.apply(find(v));
|
||||
}
|
||||
|
||||
public boolean isInRange(int idx) {
|
||||
return 0 <= idx && idx < size;
|
||||
}
|
||||
|
||||
public void sort() {
|
||||
Arrays.sort(data, 0, size);
|
||||
}
|
||||
|
||||
public void copyInto(int[] dest) {
|
||||
System.arraycopy(data, 0, dest, 0, size);
|
||||
}
|
||||
|
||||
public void removeValue(int n) {
|
||||
int idx = find(n);
|
||||
if (idx < 0) return;
|
||||
System.arraycopy(data, idx + 1, data, idx, size - (idx + 1));
|
||||
size--;
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright (c) 2012, 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 jenkins.model.lazy;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link List} decorator that provides a number of binary-search related methods
|
||||
* by assuming that the array is sorted in the ascending order.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
class SortedList<T extends Comparable<T>> extends AbstractList<T> {
|
||||
private List<T> data;
|
||||
|
||||
SortedList(List<T> data) {
|
||||
this.data = new ArrayList<>(data);
|
||||
assert isSorted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search to find the position of the given string.
|
||||
*
|
||||
* @return
|
||||
* -(insertionPoint+1) if the exact string isn't found.
|
||||
* That is, -1 means the probe would be inserted at the very beginning.
|
||||
*/
|
||||
public int find(T probe) {
|
||||
return Collections.binarySearch(data, probe);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return find((T) o) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int idx) {
|
||||
return data.get(idx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(int index) {
|
||||
return data.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return data.remove(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the entry lower than v.
|
||||
*
|
||||
* @return
|
||||
* return value will be in the [-1,size) range
|
||||
*/
|
||||
public int lower(T v) {
|
||||
return Boundary.LOWER.apply(find(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the entry greater than v.
|
||||
*
|
||||
* @return
|
||||
* return value will be in the [0,size] range
|
||||
*/
|
||||
public int higher(T v) {
|
||||
return Boundary.HIGHER.apply(find(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the entry lower or equal to v.
|
||||
*
|
||||
* @return
|
||||
* return value will be in the [-1,size) range
|
||||
*/
|
||||
public int floor(T v) {
|
||||
return Boundary.FLOOR.apply(find(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the entry greater or equal to v.
|
||||
*
|
||||
* @return
|
||||
* return value will be in the [0,size] range
|
||||
*/
|
||||
public int ceil(T v) {
|
||||
return Boundary.CEIL.apply(find(v));
|
||||
}
|
||||
|
||||
public boolean isInRange(int idx) {
|
||||
return 0 <= idx && idx < data.size();
|
||||
}
|
||||
|
||||
private boolean isSorted() {
|
||||
for (int i = 1; i < data.size(); i++) {
|
||||
if (data.get(i).compareTo(data.get(i - 1)) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -26,15 +26,15 @@ package jenkins.model.queue;
|
|||
|
||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
import hudson.model.Item;
|
||||
import hudson.model.ModelObject;
|
||||
import hudson.security.AccessControlled;
|
||||
import jenkins.model.FullyNamedModelObject;
|
||||
|
||||
/**
|
||||
* A task that can be displayed in the executors widget.
|
||||
*
|
||||
* @since 2.480
|
||||
*/
|
||||
public interface ITask extends ModelObject {
|
||||
public interface ITask extends FullyNamedModelObject {
|
||||
/**
|
||||
* @return {@code true} if the current user can cancel the current task.
|
||||
*
|
||||
|
|
|
@ -3,9 +3,9 @@ package jenkins.model.queue;
|
|||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import hudson.model.Cause;
|
||||
import hudson.model.ModelObject;
|
||||
import hudson.model.Queue;
|
||||
import hudson.model.Run;
|
||||
import jenkins.model.FullyNamedModelObject;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.Beta;
|
||||
|
||||
|
@ -14,7 +14,7 @@ import org.kohsuke.accmod.restrictions.Beta;
|
|||
* @since 2.405
|
||||
*/
|
||||
@Restricted(Beta.class)
|
||||
public interface QueueItem extends ModelObject {
|
||||
public interface QueueItem extends FullyNamedModelObject {
|
||||
/**
|
||||
* @return true if the item is starving for an executor for too long.
|
||||
*/
|
||||
|
@ -73,6 +73,15 @@ public interface QueueItem extends ModelObject {
|
|||
@CheckForNull
|
||||
@Override
|
||||
default String getDisplayName() {
|
||||
// TODO review usage of this method and replace with getFullDisplayName() where appropriate
|
||||
return getTask().getFullDisplayName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the full display name for this queue item; by default, {@link Queue.Task#getFullDisplayName()}
|
||||
*/
|
||||
@Override
|
||||
default String getFullDisplayName() {
|
||||
return getTask().getFullDisplayName();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package jenkins.util;
|
|||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import jenkins.ClassLoaderReflectionToolkit;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||
|
@ -12,6 +14,10 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
|
|||
*/
|
||||
@Restricted(NoExternalUse.class)
|
||||
public class URLClassLoader2 extends URLClassLoader implements JenkinsClassLoader {
|
||||
private static final AtomicInteger NEXT_INSTANCE_NUMBER = new AtomicInteger(0);
|
||||
|
||||
private final String lockObjectPrefixName = String.format(
|
||||
"%s@%x-loadClassLock:", URLClassLoader2.class.getName(), NEXT_INSTANCE_NUMBER.getAndIncrement());
|
||||
|
||||
static {
|
||||
registerAsParallelCapable();
|
||||
|
@ -69,8 +75,25 @@ public class URLClassLoader2 extends URLClassLoader implements JenkinsClassLoade
|
|||
return super.findLoadedClass(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the JDK's per-name lock map with a GC-collectable lock object. This is a workaround
|
||||
* for JDK-8005233. When JDK-8005233 is resolved, this should be deleted. See also the
|
||||
* discussion in <a
|
||||
* href="https://mail.openjdk.org/pipermail/core-libs-dev/2025-May/146392.html">this OpenJDK
|
||||
* thread</a>.
|
||||
*
|
||||
* <p>Parallel-capable {@link ClassLoader} implementations keep a distinct lock object per class
|
||||
* name indefinitely, which can retain huge maps when there are many misses. Returning an
|
||||
* interned {@link String} keyed by this loader and the class name preserves mutual exclusion
|
||||
* for a given (loader, name) pair but allows the JVM to reclaim the lock when no longer
|
||||
* referenced. Interned Strings are heap objects and GC-eligible on modern JDKs (7+).
|
||||
*
|
||||
* @param className the binary name of the class being loaded (must not be null)
|
||||
* @return a lock object unique to this classloader/class pair
|
||||
*/
|
||||
@Override
|
||||
public Object getClassLoadingLock(String className) {
|
||||
return super.getClassLoadingLock(className);
|
||||
Objects.requireNonNull(className);
|
||||
return (lockObjectPrefixName + className).intern();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,36 +24,70 @@
|
|||
|
||||
package org.acegisecurity.util;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* @deprecated use {@link org.apache.commons.lang.reflect.FieldUtils}
|
||||
* @deprecated Add a dependency to commons-lang3-api plugin and use {@code org.apache.commons.lang3.reflect.FieldUtils}
|
||||
*/
|
||||
@Deprecated
|
||||
public final class FieldUtils {
|
||||
|
||||
public static Object getProtectedFieldValue(String protectedField, Object object) {
|
||||
public static Object getProtectedFieldValue(@NonNull String protectedField, @NonNull Object object) {
|
||||
try {
|
||||
return org.apache.commons.lang.reflect.FieldUtils.readField(object, protectedField, true);
|
||||
Field field = getField(object.getClass(), protectedField);
|
||||
return field.get(object);
|
||||
} catch (IllegalAccessException x) {
|
||||
throw new RuntimeException(x);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setProtectedFieldValue(String protectedField, Object object, Object newValue) {
|
||||
public static void setProtectedFieldValue(@NonNull String protectedField, @NonNull Object object, @NonNull Object newValue) {
|
||||
try {
|
||||
// acgegi would silently fail to write to final fields
|
||||
// FieldUtils.writeField(Object, field, true) only sets accessible on *non* public fields
|
||||
// and then fails with IllegalAccessException (even if you make the field accessible in the interim!
|
||||
// for backwards compatability we need to use a few steps
|
||||
Field field = org.apache.commons.lang.reflect.FieldUtils.getField(object.getClass(), protectedField, true);
|
||||
field.setAccessible(true);
|
||||
Field field = getField(object.getClass(), protectedField);
|
||||
field.set(object, newValue);
|
||||
} catch (Exception x) {
|
||||
} catch (IllegalAccessException x) {
|
||||
throw new RuntimeException(x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the field with the given name from the class or its superclasses.
|
||||
* If the field is not found, an {@link IllegalArgumentException} is thrown.
|
||||
*
|
||||
* @param clazz the class to search for the field
|
||||
* @param fieldName the name of the field to find
|
||||
* @return the {@link Field} object representing the field
|
||||
* @throws IllegalArgumentException if the field is not found
|
||||
*/
|
||||
private static Field getField(@NonNull final Class<?> clazz, @NonNull final String fieldName) {
|
||||
// Check class and its superclasses
|
||||
Class<?> current = clazz;
|
||||
while (current != null) {
|
||||
try {
|
||||
Field field = current.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Continue to check superclass
|
||||
}
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
|
||||
// Check interfaces
|
||||
for (Class<?> iface : clazz.getInterfaces()) {
|
||||
try {
|
||||
Field field = iface.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
// Continue to check next interface
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Field '" + fieldName + "' not found in class " + clazz.getName());
|
||||
}
|
||||
|
||||
// TODO other methods as needed
|
||||
|
||||
private FieldUtils() {}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright (c) 2004-2019, Sun Microsystems, Inc., Damian Szczepanik
|
||||
# Copyright (c) 2004-2025, Sun Microsystems, Inc., Damian Szczepanik
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -20,3 +20,10 @@
|
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
Welcome\ to\ Jenkins!=Witamy w Jenkinsie!
|
||||
noJobDescription=To jest miejsce, gdzie projekty Jenkinsa będą wyświetlane. Aby rozpocząć, możesz skonfigurować rozproszone zadania albo zacząć budować swoje oprogramowanie.
|
||||
setUpDistributedBuilds=Skonfiguruj rozproszone zadania
|
||||
setUpAgent=Skonfiguruj agenta
|
||||
setUpCloud=Skonfiguruj chmurę
|
||||
learnMoreDistributedBuilds=Dowiedz się więcej o rozproszonych zadaniach
|
||||
startBuilding=Zacznij budować swoje oprogramowanie
|
||||
createJob=Utwórz projekt
|
||||
|
|
|
@ -28,6 +28,7 @@ THE SOFTWARE.
|
|||
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
|
||||
<j:set var="escapeEntryTitleAndDescription" value="false"/>
|
||||
<f:entry description="${it.formattedDescription}">
|
||||
<f:checkbox title="${h.escape(it.name)}" name="value" checked="${it.value}" readonly="true" />
|
||||
<j:set var="readOnlyMode" value="true"/>
|
||||
<f:checkbox title="${h.escape(it.name)}" name="value" checked="${it.value}"/>
|
||||
</f:entry>
|
||||
</j:jelly>
|
||||
|
|
|
@ -199,8 +199,7 @@ LabelExpression.LabelLink=<a href="{0}{2}">Label {1}</a> matches {3,choice,0#no
|
|||
LabelExpression.NoMatch=No agent/cloud matches this label expression.
|
||||
LabelExpression.NoMatch_DidYouMean=No agent/cloud matches this label expression. Did you mean ‘{1}’ instead of ‘{0}’?
|
||||
ManageJenkinsAction.DisplayName=Manage Jenkins
|
||||
ManageJenkinsAction.notification={0} notification
|
||||
ManageJenkinsAction.notifications={0} notifications
|
||||
ManageJenkinsAction.notifications=One or more notifications
|
||||
MultiStageTimeSeries.EMPTY_STRING=
|
||||
ParametersDefinitionProperty.BuildButtonText=Build
|
||||
Queue.AllNodesOffline=All nodes of label ‘{0}’ are offline
|
||||
|
|
|
@ -35,6 +35,7 @@ THE SOFTWARE.
|
|||
<l:main-panel>
|
||||
<t:buildCaption it="${build}">${title}</t:buildCaption>
|
||||
<j:set var="escapeEntryTitleAndDescription" value="true" /> <!-- SECURITY-353 defense unless overridden -->
|
||||
<j:set var="readOnlyMode" value="true"/>
|
||||
<j:forEach var="parameterValue" items="${it.parameters}">
|
||||
<st:include it="${parameterValue}" page="value.jelly" />
|
||||
</j:forEach>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright (c) 2004-2010, Sun Microsystems, Inc.
|
||||
# Copyright (c) 2004-2025, Sun Microsystems, 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
|
||||
|
@ -24,3 +24,4 @@ Description=Opis
|
|||
DisplayName=Wyświetlana nazwa
|
||||
LOADING=ŁADOWANIE
|
||||
Save=Zapisz
|
||||
Edit\ Build\ Information=Edycja informacji o zadaniu
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright (c) 2004-2016, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors
|
||||
# Copyright (c) 2004-2025, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -21,4 +21,7 @@
|
|||
# THE SOFTWARE.
|
||||
|
||||
Console\ Output=Logi konsoli
|
||||
Download=Pobierz
|
||||
Copy=Kopiuj
|
||||
skipSome=Pominięto {0,number,integer} KB.. <a href="{1}">Pokaż wszystko</a>
|
||||
View\ as\ plain\ text=Wyświetl bez formatowania
|
||||
|
|
|
@ -24,10 +24,11 @@ THE SOFTWARE.
|
|||
|
||||
<?jelly escape-by-default='true'?>
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
|
||||
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
|
||||
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
|
||||
<j:set var="escapeEntryTitleAndDescription" value="false"/>
|
||||
<f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
|
||||
<f:textbox name="value" value="${it.value}" readonly="true" />
|
||||
</f:entry>
|
||||
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
|
||||
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
|
||||
<j:set var="escapeEntryTitleAndDescription" value="false"/>
|
||||
<f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
|
||||
<j:set var="readOnlyMode" value="true"/>
|
||||
<f:textbox name="value" value="${it.value}"/>
|
||||
</f:entry>
|
||||
</j:jelly>
|
|
@ -26,8 +26,9 @@ THE SOFTWARE.
|
|||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
|
||||
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
|
||||
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
|
||||
<j:set var="escapeEntryTitleAndDescription" value="false"/>
|
||||
<f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
|
||||
<f:textarea name="value" value="${it.value}" readonly="readonly" />
|
||||
</f:entry>
|
||||
<j:set var="escapeEntryTitleAndDescription" value="false"/>
|
||||
<f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
|
||||
<j:set var="readOnlyMode" value="true"/>
|
||||
<f:textarea name="value" value="${it.value}"/>
|
||||
</f:entry>
|
||||
</j:jelly>
|
|
@ -0,0 +1,27 @@
|
|||
<!--
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2025 Jan Faracik
|
||||
|
||||
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.
|
||||
-->
|
||||
<?jelly escape-by-default='true'?>
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:l="/lib/layout">
|
||||
<l:app-bar title="${it.fullName}" icon="${h.getUserAvatar(it, '96x96')}" />
|
||||
</j:jelly>
|
|
@ -27,9 +27,7 @@ THE SOFTWARE.
|
|||
<st:include page="sidepanel.jelly" />
|
||||
<l:breadcrumb title="${%Builds}" />
|
||||
<l:main-panel>
|
||||
<h1>
|
||||
${%title(it)}
|
||||
</h1>
|
||||
<l:app-bar title="${%Builds}" />
|
||||
|
||||
<t:buildListTable builds="${it.builds}"/>
|
||||
</l:main-panel>
|
||||
|
|
|
@ -28,12 +28,7 @@ THE SOFTWARE.
|
|||
<!-- no need for additional breadcrumb here as we're on an index page already including breadcrumb -->
|
||||
<l:main-panel>
|
||||
<div class="jenkins-app-bar">
|
||||
<div class="jenkins-app-bar__content jenkins-build-caption">
|
||||
<l:icon src="${h.getUserAvatar(it, '96x96')}" class="jenkins-avatar" />
|
||||
<h1>
|
||||
${it.fullName}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="jenkins-app-bar__content" />
|
||||
<div class="jenkins-app-bar__controls">
|
||||
<t:editDescriptionButton permission="${app.ADMINISTER}"/>
|
||||
</div>
|
||||
|
|
|
@ -30,8 +30,10 @@ THE SOFTWARE.
|
|||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
|
||||
<l:header />
|
||||
<l:side-panel>
|
||||
<st:include page="app-bar.jelly" />
|
||||
|
||||
<l:tasks>
|
||||
<l:task contextMenu="false" href="${rootURL}/${it.url}/" icon="symbol-person-circle" title="${%Status}"/>
|
||||
<l:task contextMenu="false" href="${rootURL}/${it.url}/" icon="symbol-person" title="${%Profile}"/>
|
||||
<l:task href="${rootURL}/${it.url}/builds" icon="symbol-build-history" title="${%Builds}"/>
|
||||
<t:actions actions="${it.propertyActions}"/>
|
||||
<t:actions actions="${it.transientActions}"/>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright (c) 2004-2016, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors
|
||||
# Copyright (c) 2004-2025, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -25,3 +25,4 @@ Configure=Konfiguracja
|
|||
Delete=Usuń
|
||||
People=Użytkownicy
|
||||
Status=Status
|
||||
Profile=Profil
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright (c) 2004-2016, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors
|
||||
# Copyright (c) 2004-2025, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -25,6 +25,7 @@ ItemName.help=Pole wymagane
|
|||
ItemName.label=Podaj nazwę projektu
|
||||
ItemName.validation.required=To pole nie może być puste, podaj nazwę projektu
|
||||
ItemType.validation.required=Wybierz rodzaj projektu
|
||||
ItemType.label=Wybierz rodzaj projektu
|
||||
CopyOption.placeholder=Podaj nazwę
|
||||
CopyOption.description=Jeśli chcesz stworzyć nowy projekt na podstawie istniejącego, możesz użyć tej opcji:
|
||||
CopyOption.label=Kopiuj z
|
||||
|
|
|
@ -35,6 +35,9 @@ THE SOFTWARE.
|
|||
<link rel="alternate" title="Jenkins:${it.viewName} (failed builds) (RSS 2.0)" href="${rootURL}/${it.url}rssFailed?flavor=rss20" type="application/rss+xml" />
|
||||
</l:header>
|
||||
<l:side-panel>
|
||||
<!-- Display the user's name for their views -->
|
||||
<st:include page="app-bar.jelly" it="${it.owner.user}" optional="true" />
|
||||
|
||||
<l:tasks>
|
||||
<st:include page="tasks-top.jelly" it="${it.owner}" optional="true" />
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright 2025 Damian Szczepanik
|
||||
#
|
||||
# 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.
|
||||
UserPropertyCategory.Account.DisplayName=Konto
|
||||
UserPropertyCategory.Preferences.DisplayName=Ustawienia
|
||||
UserPropertyCategory.Experimental.DisplayName=Eksperymenty
|
||||
UserPropertyCategory.Appearance.DisplayName=Wygląd
|
||||
UserPropertyCategory.Security.DisplayName=Bezpieczeństwo
|
||||
UserPropertyCategory.Invisible.DisplayName=Niewidoczny
|
||||
UserPropertyCategoryAccountAction.DisplayName=Konto
|
||||
UserPropertyCategoryAppearanceAction.DisplayName=Wygląd
|
||||
UserPropertyCategoryExperimentalAction.DisplayName=Eksperymenty
|
||||
UserPropertyCategoryPreferencesAction.DisplayName=Ustawienia
|
||||
UserPropertyCategorySecurityAction.DisplayName=Bezpieczeństwo
|
|
@ -30,16 +30,14 @@ THE SOFTWARE.
|
|||
<l:layout permission="${app.ADMINISTER}" title="${%title}">
|
||||
<st:include page="sidepanel.jelly" it="${it.targetUser}" />
|
||||
<l:main-panel>
|
||||
<f:form method="post" action="configSubmit" name="config" class="jenkins-form">
|
||||
<h1>
|
||||
${%title}
|
||||
</h1>
|
||||
<l:app-bar title="${%title}" />
|
||||
|
||||
<f:form method="post" action="configSubmit" name="config" class="jenkins-form">
|
||||
<j:set var="thisAction" value="${it}" />
|
||||
<j:set var="it" value="${thisAction.targetUser}" />
|
||||
<j:set var="instance" value="${it}"/>
|
||||
|
||||
<f:section>
|
||||
<f:section title="${%General}">
|
||||
<f:entry title="${%Full name}" description="${%Full name.Description}">
|
||||
<f:textbox field="fullName" />
|
||||
</f:entry>
|
||||
|
|
|
@ -30,11 +30,9 @@ THE SOFTWARE.
|
|||
<l:layout permission="${app.ADMINISTER}" title="${%title}">
|
||||
<st:include page="sidepanel.jelly" it="${it.targetUser}" />
|
||||
<l:main-panel>
|
||||
<l:app-bar title="${%title}" />
|
||||
|
||||
<f:form method="post" action="configSubmit" name="config" class="jenkins-form">
|
||||
<h1>
|
||||
${%title}
|
||||
</h1>
|
||||
|
||||
<j:set var="instance" value="${it}"/>
|
||||
<j:set var="descriptors" value="${it.myCategoryDescriptors}" />
|
||||
<j:set var="instances" value="${it.targetUser.properties}" />
|
||||
|
|
|
@ -30,11 +30,9 @@ THE SOFTWARE.
|
|||
<l:layout permission="${app.ADMINISTER}" title="${%title}">
|
||||
<st:include page="sidepanel.jelly" it="${it.targetUser}" />
|
||||
<l:main-panel>
|
||||
<l:app-bar title="${%title}" />
|
||||
|
||||
<f:form method="post" action="configSubmit" name="config">
|
||||
<h1>
|
||||
${%title}
|
||||
</h1>
|
||||
|
||||
<j:set var="instance" value="${it}"/>
|
||||
<j:set var="descriptors" value="${it.myCategoryDescriptors}" />
|
||||
<j:set var="instances" value="${it.targetUser.properties}" />
|
||||
|
|
|
@ -30,11 +30,9 @@ THE SOFTWARE.
|
|||
<l:layout permission="${app.ADMINISTER}" title="${%title}">
|
||||
<st:include page="sidepanel.jelly" it="${it.targetUser}" />
|
||||
<l:main-panel>
|
||||
<f:form method="post" action="configSubmit" name="config">
|
||||
<h1>
|
||||
${%title}
|
||||
</h1>
|
||||
<l:app-bar title="${%title}" />
|
||||
|
||||
<f:form method="post" action="configSubmit" name="config">
|
||||
<j:set var="instance" value="${it}"/>
|
||||
<j:set var="descriptors" value="${it.myCategoryDescriptors}" />
|
||||
<j:set var="instances" value="${it.targetUser.properties}" />
|
||||
|
|
|
@ -30,11 +30,9 @@ THE SOFTWARE.
|
|||
<l:layout permission="${app.ADMINISTER}" title="${%title}">
|
||||
<st:include page="sidepanel.jelly" it="${it.targetUser}" />
|
||||
<l:main-panel>
|
||||
<f:form method="post" action="configSubmit" name="config">
|
||||
<h1>
|
||||
${%title}
|
||||
</h1>
|
||||
<l:app-bar title="${%title}" />
|
||||
|
||||
<f:form method="post" action="configSubmit" name="config">
|
||||
<j:set var="instance" value="${it}"/>
|
||||
<j:set var="descriptors" value="${it.myCategoryDescriptors}" />
|
||||
<j:set var="instances" value="${it.targetUser.properties}" />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright (c) 2004-2022, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors
|
||||
# Copyright (c) 2004-2025, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -25,3 +25,4 @@ for\ failures=dla nieudanych
|
|||
Clear=wyczyść
|
||||
trend=trend
|
||||
find=szukaj
|
||||
No\ builds=Brak zadań
|
||||
|
|
|
@ -1 +1,23 @@
|
|||
IOfflineCause.offline=rozłączony
|
||||
# The MIT License
|
||||
#
|
||||
# Copyright (c) 2025, Damian Szczepanik
|
||||
#
|
||||
# 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.
|
||||
CloudsLink.DisplayName=Chmura
|
||||
IOfflineCause.offline=Offline
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright 2025 Damian Szczepanik
|
||||
#
|
||||
# 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.
|
||||
SetupWizard_ConfigureInstance_ValidationErrors=Niektóre ustawienia są niepoprawne. Sprawdź komunikat błędu, aby zobaczyć szczegóły.
|
||||
SetupWizard_ConfigureInstance_RootUrl_Empty=Adres URL nie może być pusty
|
||||
SetupWizard_ConfigureInstance_RootUrl_Invalid=Adres URL jest niepoprawny, upewnij się, że używasz http:// lub https:// wraz z poprawnym adresem domeny.
|
||||
SetupWizard.DisplayName=Kreator konfiguracji
|
|
@ -66,5 +66,3 @@ ShutdownLink.Description=Stops executing new builds, so that the system can be e
|
|||
ShutdownLink.ShuttingDownInProgressDescription=Jenkins is currently shutting down. New builds are not executing.
|
||||
ShutdownLink.ShutDownReason_title=Reason
|
||||
ShutdownLink.ShutDownReason_update=Update reason
|
||||
|
||||
AdministrativeMonitorsDecorator.DisplayName=Administrative Monitors Notifier
|
||||
|
|
|
@ -74,6 +74,3 @@ ShutdownLink.Description=\
|
|||
# Configure tools, their locations and automatic installers.
|
||||
ConfigureTools.Description=\
|
||||
Настройване на инструментите, местоположенията и автоматичното инсталиране.
|
||||
# Administrative Monitors Notifier
|
||||
AdministrativeMonitorsDecorator.DisplayName=\
|
||||
Известия за предупреждения
|
||||
|
|
|
@ -49,5 +49,4 @@ NodesLink.Description=Knoten hinzufügen, entfernen, steuern und überwachen, au
|
|||
CliLink.Description=Jenkins aus der Kommandozeile oder skriptgesteuert nutzen und verwalten.
|
||||
CliLink.DisplayName=Jenkins CLI
|
||||
SystemLogLink.DisplayName=Systemlog
|
||||
AdministrativeMonitorsDecorator.DisplayName=Anzeige aktiver Administrator-Warnungen
|
||||
ConfigureTools.Description=Hilfsprogramme, ihre Installationsverzeichnisse und Installationsverfahren konfigurieren
|
||||
|
|
|
@ -21,8 +21,6 @@
|
|||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
AdministrativeMonitorsDecorator.DisplayName=Componente di notifica monitor \
|
||||
amministrativi
|
||||
CliLink.Description=Accedi/gestisci Jenkins dal terminale o da uno script.
|
||||
CliLink.DisplayName=Interfaccia a riga di comando di Jenkins
|
||||
ConfigureLink.Description=Configura le impostazioni e i percorsi globali.
|
||||
|
|
|
@ -45,7 +45,6 @@ NodesLink.Description=Adiciona, remove, controla e monitora o vários nós
|
|||
CliLink.DisplayName=Interface de Linha de Commando do Jenkins (CLI)
|
||||
ShutdownLink.DisplayName_update=Atualizar preparação de desligamento
|
||||
ShutdownLink.ShutDownReason_update=Atualizar razão
|
||||
AdministrativeMonitorsDecorator.DisplayName=Notificador de monitorações administrativas
|
||||
ConfigureTools.Description=Configurar ferramentas, suas localizações e instaladores automáticos.
|
||||
ShutdownLink.ShuttingDownInProgressDescription=O Jenkins está sendo desligado no momento. Novas construções não serão executadas.
|
||||
ShutdownLink.ShutDownReason_title=Razão
|
||||
|
|
|
@ -41,4 +41,3 @@ NodesLink.Description=Позволяет добавлять, удалять, к
|
|||
PluginsLink.Description=Добавить, удалить, отключить или включить плагины, расширяющие функционональные возможности Jenkins.
|
||||
ConfigureTools.Description=Конфигурация инструментов, их расположение и автоматическая инсталяция.
|
||||
SystemLogLink.DisplayName=Системный журнал
|
||||
AdministrativeMonitorsDecorator.DisplayName=Системные уведомления
|
||||
|
|
|
@ -66,5 +66,3 @@ ShutdownLink.Description=Slutar köra nya byggen så att systemet eventuellt kan
|
|||
ShutdownLink.ShuttingDownInProgressDescription=Jenkins stängs ned för tillfället. Nya byggen körs inte.
|
||||
ShutdownLink.ShutDownReason_title=Anledning
|
||||
ShutdownLink.ShutDownReason_update=Uppdatera anledning
|
||||
|
||||
AdministrativeMonitorsDecorator.DisplayName=Avisering om administrativ övervakning
|
||||
|
|
|
@ -58,5 +58,3 @@ ShutdownLink.Description=不再執行新的建置作業,讓系統可以安全
|
|||
ShutdownLink.ShuttingDownInProgressDescription=Jenkins 正在停機,不會執行新的建置作業。
|
||||
ShutdownLink.ShutDownReason_title=原因
|
||||
ShutdownLink.ShutDownReason_update=更新原因
|
||||
|
||||
AdministrativeMonitorsDecorator.DisplayName=管理監視器通知
|
||||
|
|
|
@ -41,7 +41,7 @@ THE SOFTWARE.
|
|||
|
||||
<h2>Restarting Jenkins</h2>
|
||||
<p>
|
||||
Jenkins will enter into the "quiet down" mode by sending a POST request with optional <code>reason</code> query parameter to <a href="../quietDown">this URL</a>.
|
||||
Jenkins will enter into the "quiet down" mode by sending a POST request with optional <code>message</code> query parameter as the reason to <a href="../quietDown">this URL</a>.
|
||||
You can also send another request to this URL to update the reason.
|
||||
You can cancel this mode by sending a POST request to <a href="../cancelQuietDown">this URL</a>. On environments
|
||||
where Jenkins can restart itself (such as when Jenkins is installed as a Windows service), POSTing to
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# The MIT License
|
||||
#
|
||||
# Copyright (c) 2013-2019, Sun Microsystems, Inc., Kohsuke Kawaguchi
|
||||
# Copyright (c) 2013-2025, Sun Microsystems, Inc., Kohsuke Kawaguchi
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -23,6 +23,7 @@
|
|||
Password=Hasło
|
||||
signUp=Zaloguj się poniżej lub <a href="signup">załóż konto</a>.
|
||||
signIn=Zaloguj
|
||||
Sign\ in\ to\ Jenkins=Zaloguj się do Jenkinsa
|
||||
Keep\ me\ signed\ in=Zapamiętaj mnie
|
||||
Username=Użytkownik
|
||||
Invalid\ username\ or\ password=Niepoprawny użytkownik lub hasło
|
||||
|
|
|
@ -24,12 +24,14 @@ THE SOFTWARE.
|
|||
|
||||
<?jelly escape-by-default='true'?>
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
|
||||
<select class="jenkins-select__input" name="[${it.flagKey}]">
|
||||
<f:option selected="${flagValue == null}" value="">
|
||||
<j:if test="${it.getDefaultValue() == true}">${%Default_True}</j:if>
|
||||
<j:if test="${it.getDefaultValue() == false}">${%Default_False}</j:if>
|
||||
</f:option>
|
||||
<f:option selected="${flagValue == true}" value="true">${%True}</f:option>
|
||||
<f:option selected="${flagValue == false}" value="false">${%False}</f:option>
|
||||
</select>
|
||||
<div class="jenkins-select">
|
||||
<select class="jenkins-select__input" name="[${it.flagKey}]">
|
||||
<f:option selected="${flagValue == null}" value="">
|
||||
<j:if test="${it.getDefaultValue() == true}">${%Default_True}</j:if>
|
||||
<j:if test="${it.getDefaultValue() == false}">${%Default_False}</j:if>
|
||||
</f:option>
|
||||
<f:option selected="${flagValue == true}" value="true">${%True}</f:option>
|
||||
<f:option selected="${flagValue == false}" value="false">${%False}</f:option>
|
||||
</select>
|
||||
</div>
|
||||
</j:jelly>
|
||||
|
|
|
@ -23,20 +23,20 @@ THE SOFTWARE.
|
|||
-->
|
||||
|
||||
<?jelly escape-by-default='true'?>
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:f="/lib/form">
|
||||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:f="/lib/form" xmlns:l="/lib/layout">
|
||||
<j:set var="userProperty" value="${instance}"/>
|
||||
<f:entry field="experimentalFlags">
|
||||
<j:invokeStatic var="flagConfigs" className="jenkins.model.experimentalflags.UserExperimentalFlag"
|
||||
method="all"/>
|
||||
|
||||
<j:if test="${empty(flagConfigs)}">
|
||||
<div class="jenkins-form-item">
|
||||
${%NoFlagInfo}
|
||||
</div>
|
||||
</j:if>
|
||||
<j:if test="${!empty(flagConfigs)}">
|
||||
<j:invokeStatic var="flagConfigs" className="jenkins.model.experimentalflags.UserExperimentalFlag"
|
||||
method="all"/>
|
||||
|
||||
<j:if test="${empty(flagConfigs)}">
|
||||
<l:notice icon="symbol-flask"
|
||||
title="${%NoFlagInfo}" />
|
||||
</j:if>
|
||||
|
||||
<j:if test="${!empty(flagConfigs)}">
|
||||
<f:entry field="experimentalFlags">
|
||||
<f:rowSet name="flags">
|
||||
<table class="jenkins-table sortable">
|
||||
<table class="jenkins-table sortable jenkins-!-margin-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${%FlagDisplayName}</th>
|
||||
|
@ -62,6 +62,6 @@ THE SOFTWARE.
|
|||
</tbody>
|
||||
</table>
|
||||
</f:rowSet>
|
||||
</j:if>
|
||||
</f:entry>
|
||||
</f:entry>
|
||||
</j:if>
|
||||
</j:jelly>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue