From c3988aae8ad66fde529830e799fe3d49cd241ebb Mon Sep 17 00:00:00 2001 From: Nicolas De loof Date: Sat, 18 Aug 2018 10:49:44 +0200 Subject: [PATCH] [JENKINS-52818] support @Postconstruct on extensions (#3570) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add support for JSR250 @PostConstruct Signed-off-by: Nicolas De Loof * with PostConstruct method no need to explicitely invoke load() anymore Signed-off-by: Nicolas De Loof * introduce PersistentDescriptor to flag Descriptors which expect load() to be invoked. This prevent double execution for plugins which have been designed on previous version of code and don’t expect load to be automatically invoked. Signed-off-by: Nicolas De Loof * document PostConstruct method discovery logic Signed-off-by: Nicolas De Loof * Force reflective access to non-public @PostConstruct methods Signed-off-by: Nicolas De Loof * check @PostConstruct annotation both on method and on implemented interface with a matching method. Signed-off-by: Nicolas De Loof * typo Signed-off-by: Nicolas De Loof --- .../src/main/java/hudson/ExtensionFinder.java | 88 ++++++++++++++++--- .../main/java/hudson/model/Descriptor.java | 4 + .../hudson/model/PersistentDescriptor.java | 16 ++++ .../java/hudson/model/UsageStatistics.java | 3 +- .../security/csrf/DefaultCrumbIssuer.java | 4 +- core/src/main/java/hudson/tasks/Maven.java | 4 +- core/src/main/java/hudson/tasks/Shell.java | 7 +- .../main/java/hudson/triggers/SCMTrigger.java | 11 ++- core/src/main/java/jenkins/CLI.java | 8 +- .../model/ArtifactManagerConfiguration.java | 7 +- .../java/jenkins/model/DownloadSettings.java | 7 +- .../model/JenkinsLocationConfiguration.java | 7 +- .../java/jenkins/mvn/GlobalMavenConfig.java | 7 +- .../QueueItemAuthenticatorConfiguration.java | 7 +- .../UpdateSiteWarningsConfiguration.java | 7 +- .../ApiTokenPropertyConfiguration.java | 7 +- 16 files changed, 126 insertions(+), 68 deletions(-) create mode 100644 core/src/main/java/hudson/model/PersistentDescriptor.java diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index 5d7d13d8f4..1ff0638c10 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -23,6 +23,7 @@ */ package hudson; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -35,11 +36,13 @@ import com.google.inject.Module; import com.google.inject.Provider; import com.google.inject.Scope; import com.google.inject.Scopes; +import com.google.inject.matcher.Matchers; import com.google.inject.name.Names; -import com.google.common.collect.ImmutableList; +import com.google.inject.spi.ProvisionListener; import hudson.init.InitMilestone; import hudson.model.Descriptor; import hudson.model.Hudson; +import hudson.util.ReflectionUtils; import jenkins.ExtensionComponentSet; import jenkins.ExtensionFilter; import jenkins.ExtensionRefreshException; @@ -49,20 +52,25 @@ import net.java.sezpoz.Index; import net.java.sezpoz.IndexItem; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.util.ClassUtils; +import javax.annotation.PostConstruct; import java.lang.annotation.Annotation; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.logging.Logger; -import java.util.logging.Level; -import java.util.List; -import java.util.ArrayList; -import java.util.Collection; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Discovers the implementations of an extension point. @@ -454,7 +462,7 @@ public abstract class ExtensionFinder implements ExtensionPoint { * Instead of using SezPoz to instantiate, we'll instantiate them by using Guice, * so that we can take advantage of dependency injection. */ - private class SezpozModule extends AbstractModule { + private class SezpozModule extends AbstractModule implements ProvisionListener { private final List> index; private final List> loadedIndex; @@ -503,6 +511,9 @@ public abstract class ExtensionFinder implements ExtensionPoint { @SuppressWarnings({"unchecked", "ChainOfInstanceofChecks"}) @Override protected void configure() { + + bindListener(Matchers.any(), this); + for (final IndexItem item : index) { boolean optional = isOptional(item.annotation()); try { @@ -550,9 +561,64 @@ public abstract class ExtensionFinder implements ExtensionPoint { public List> getLoadedIndex() { return Collections.unmodifiableList(loadedIndex); } + + @Override + public void onProvision(ProvisionInvocation provision) { + final T instance = provision.provision(); + if (instance == null) return; + List methods = new LinkedList<>(); + Class c = instance.getClass(); + + // find PostConstruct methods in class hierarchy, the one from parent class being first in list + // so that we invoke them before derived class one. This isn't specified in JSR-250 but implemented + // this way in Spring and what most developers would expect to happen. + + final Set interfaces = ClassUtils.getAllInterfacesAsSet(instance); + + while (c != Object.class) { + Arrays.stream(c.getDeclaredMethods()) + .map(m -> getMethodAndInterfaceDeclarations(m, interfaces)) + .flatMap(Collection::stream) + .filter(m -> m.getAnnotation(PostConstruct.class) != null) + .findFirst() + .ifPresent(method -> methods.add(0, method)); + c = c.getSuperclass(); + } + + for (Method postConstruct : methods) { + try { + postConstruct.setAccessible(true); + postConstruct.invoke(instance); + } catch (final Exception e) { + throw new RuntimeException(String.format("@PostConstruct %s", postConstruct), e); + } + } + } } } + /** + * Returns initial {@link Method} as well as all matching ones found in interfaces. + * This allows to introspect metadata for a method which is both declared in parent class and in implemented + * interface(s). interfaces typically is obtained by {@link ClassUtils#getAllInterfacesAsSet} + */ + Collection getMethodAndInterfaceDeclarations(Method method, Collection interfaces) { + final List methods = new ArrayList<>(); + methods.add(method); + + // we search for matching method by iteration and comparison vs getMethod to avoid repeated NoSuchMethodException + // being thrown, while interface typically only define a few set of methods to check. + interfaces.stream() + .map(Class::getMethods) + .flatMap(Arrays::stream) + .filter(m -> m.getName().equals(method.getName()) && Arrays.equals(m.getParameterTypes(), method.getParameterTypes())) + .findFirst() + .ifPresent(methods::add); + + return methods; + } + + /** * The bootstrap implementation that looks for the {@link Extension} marker. * diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java index aef728d373..f8e4c2cfeb 100644 --- a/core/src/main/java/hudson/model/Descriptor.java +++ b/core/src/main/java/hudson/model/Descriptor.java @@ -51,6 +51,8 @@ import org.apache.commons.io.IOUtils; import static hudson.util.QuotedStringTokenizer.*; import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; + +import javax.annotation.PostConstruct; import javax.servlet.ServletException; import javax.servlet.RequestDispatcher; import java.io.File; @@ -117,6 +119,8 @@ import javax.annotation.Nullable; * {@link Descriptor} can persist data just by storing them in fields. * However, it is the responsibility of the derived type to properly * invoke {@link #save()} and {@link #load()}. + * {@link #load()} is automatically invoked as a JSR-250 lifecycle method if derived class + * do implement {@link PersistentDescriptor}. * *

Reflection Enhancement

* {@link Descriptor} defines addition to the standard Java reflection diff --git a/core/src/main/java/hudson/model/PersistentDescriptor.java b/core/src/main/java/hudson/model/PersistentDescriptor.java new file mode 100644 index 0000000000..837d154cfb --- /dev/null +++ b/core/src/main/java/hudson/model/PersistentDescriptor.java @@ -0,0 +1,16 @@ +package hudson.model; + +import javax.annotation.PostConstruct; + +/** + * Marker interface for Descriptors which use xml persistent data, and as such need to load from disk when instantiated. + *

+ * {@link Descriptor#load()} method is annotated as {@link PostConstruct} so it get automatically invoked after + * constructor and field injection. + * @author Nicolas De Loof + */ +public interface PersistentDescriptor extends Saveable { + + @PostConstruct + void load(); +} diff --git a/core/src/main/java/hudson/model/UsageStatistics.java b/core/src/main/java/hudson/model/UsageStatistics.java index 2f78840b63..bb791d38e1 100644 --- a/core/src/main/java/hudson/model/UsageStatistics.java +++ b/core/src/main/java/hudson/model/UsageStatistics.java @@ -66,7 +66,7 @@ import jenkins.util.SystemProperties; * @author Kohsuke Kawaguchi */ @Extension -public class UsageStatistics extends PageDecorator { +public class UsageStatistics extends PageDecorator implements PersistentDescriptor { private final String keyImage; /** @@ -88,7 +88,6 @@ public class UsageStatistics extends PageDecorator { */ public UsageStatistics(String keyImage) { this.keyImage = keyImage; - load(); } /** diff --git a/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java b/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java index 7f833c17f3..ae4a4edfab 100644 --- a/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java +++ b/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java @@ -12,6 +12,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import hudson.Extension; +import hudson.model.PersistentDescriptor; import jenkins.util.SystemProperties; import hudson.Util; import jenkins.model.Jenkins; @@ -121,13 +122,12 @@ public class DefaultCrumbIssuer extends CrumbIssuer { } @Extension @Symbol("standard") - public static final class DescriptorImpl extends CrumbIssuerDescriptor implements ModelObject { + public static final class DescriptorImpl extends CrumbIssuerDescriptor implements ModelObject, PersistentDescriptor { private final static HexStringConfidentialKey CRUMB_SALT = new HexStringConfidentialKey(Jenkins.class,"crumbSalt",16); public DescriptorImpl() { super(CRUMB_SALT.get(), SystemProperties.getString("hudson.security.csrf.requestfield", CrumbIssuer.DEFAULT_CRUMB_NAME)); - load(); } @Override diff --git a/core/src/main/java/hudson/tasks/Maven.java b/core/src/main/java/hudson/tasks/Maven.java index 910e3a6ebf..ac36f7fcde 100644 --- a/core/src/main/java/hudson/tasks/Maven.java +++ b/core/src/main/java/hudson/tasks/Maven.java @@ -24,6 +24,7 @@ package hudson.tasks; import hudson.Extension; +import hudson.model.PersistentDescriptor; import jenkins.MasterToSlaveFileCallable; import hudson.Launcher; import hudson.Functions; @@ -424,13 +425,12 @@ public class Maven extends Builder { public static DescriptorImpl DESCRIPTOR; @Extension @Symbol("maven") - public static final class DescriptorImpl extends BuildStepDescriptor { + public static final class DescriptorImpl extends BuildStepDescriptor implements PersistentDescriptor { @CopyOnWrite private volatile MavenInstallation[] installations = new MavenInstallation[0]; public DescriptorImpl() { DESCRIPTOR = this; - load(); } public boolean isApplicable(Class jobType) { diff --git a/core/src/main/java/hudson/tasks/Shell.java b/core/src/main/java/hudson/tasks/Shell.java index 4360f4ce94..3455076d5b 100644 --- a/core/src/main/java/hudson/tasks/Shell.java +++ b/core/src/main/java/hudson/tasks/Shell.java @@ -27,6 +27,7 @@ import hudson.FilePath; import hudson.Util; import hudson.Extension; import hudson.model.AbstractProject; +import hudson.model.PersistentDescriptor; import hudson.remoting.VirtualChannel; import hudson.util.FormValidation; import java.io.IOException; @@ -131,16 +132,12 @@ public class Shell extends CommandInterpreter { } @Extension @Symbol("shell") - public static class DescriptorImpl extends BuildStepDescriptor { + public static class DescriptorImpl extends BuildStepDescriptor implements PersistentDescriptor { /** * Shell executable, or null to default. */ private String shell; - public DescriptorImpl() { - load(); - } - public boolean isApplicable(Class jobType) { return true; } diff --git a/core/src/main/java/hudson/triggers/SCMTrigger.java b/core/src/main/java/hudson/triggers/SCMTrigger.java index 7afeeacdb0..83a6383d82 100644 --- a/core/src/main/java/hudson/triggers/SCMTrigger.java +++ b/core/src/main/java/hudson/triggers/SCMTrigger.java @@ -37,6 +37,7 @@ import hudson.model.AdministrativeMonitor; import hudson.model.Cause; import hudson.model.CauseAction; import hudson.model.Item; +import hudson.model.PersistentDescriptor; import hudson.model.Run; import hudson.scm.SCM; import hudson.scm.SCMDescriptor; @@ -84,6 +85,8 @@ import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; +import javax.annotation.PostConstruct; + import static java.util.logging.Level.WARNING; @@ -211,7 +214,7 @@ public class SCMTrigger extends Trigger { } @Extension @Symbol("pollSCM") - public static class DescriptorImpl extends TriggerDescriptor { + public static class DescriptorImpl extends TriggerDescriptor implements PersistentDescriptor { private static ThreadFactory threadFactory() { return new NamingThreadFactory(Executors.defaultThreadFactory(), "SCMTrigger"); @@ -242,11 +245,6 @@ public class SCMTrigger extends Trigger { private static final int THREADS_UPPER_BOUND = 100; private static final int THREADS_DEFAULT= 10; - public DescriptorImpl() { - load(); - resizeThreadPool(); - } - private void readResolve() { if (maximumThreads == 0) { maximumThreads = THREADS_DEFAULT; @@ -347,6 +345,7 @@ public class SCMTrigger extends Trigger { /** * Update the {@link ExecutorService} instance. */ + @PostConstruct /*package*/ synchronized void resizeThreadPool() { queue.setExecutors(Executors.newFixedThreadPool(maximumThreads, threadFactory())); } diff --git a/core/src/main/java/jenkins/CLI.java b/core/src/main/java/jenkins/CLI.java index 4d9f9af3df..e0a7f38957 100644 --- a/core/src/main/java/jenkins/CLI.java +++ b/core/src/main/java/jenkins/CLI.java @@ -4,6 +4,8 @@ import hudson.Extension; import hudson.model.AdministrativeMonitor; import java.io.IOException; import javax.annotation.Nonnull; + +import hudson.model.PersistentDescriptor; import jenkins.model.GlobalConfiguration; import jenkins.model.GlobalConfigurationCategory; import org.jenkinsci.Symbol; @@ -23,7 +25,7 @@ import org.kohsuke.stapler.interceptor.RequirePOST; */ @Restricted(NoExternalUse.class) @Extension @Symbol("remotingCLI") -public class CLI extends GlobalConfiguration { +public class CLI extends GlobalConfiguration implements PersistentDescriptor { /** * Supersedes {@link #isEnabled} if set. @@ -39,10 +41,6 @@ public class CLI extends GlobalConfiguration { private boolean enabled = true; // historical default, but overridden in SetupWizard - public CLI() { - load(); - } - @Override public @Nonnull GlobalConfigurationCategory getCategory() { return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class); diff --git a/core/src/main/java/jenkins/model/ArtifactManagerConfiguration.java b/core/src/main/java/jenkins/model/ArtifactManagerConfiguration.java index 29d7439d03..cb411800d9 100644 --- a/core/src/main/java/jenkins/model/ArtifactManagerConfiguration.java +++ b/core/src/main/java/jenkins/model/ArtifactManagerConfiguration.java @@ -25,6 +25,7 @@ package jenkins.model; import hudson.Extension; +import hudson.model.PersistentDescriptor; import hudson.util.DescribableList; import java.io.IOException; import net.sf.json.JSONObject; @@ -38,7 +39,7 @@ import javax.annotation.Nonnull; * @since 1.532 */ @Extension @Symbol("artifactManager") -public class ArtifactManagerConfiguration extends GlobalConfiguration { +public class ArtifactManagerConfiguration extends GlobalConfiguration implements PersistentDescriptor { public static @Nonnull ArtifactManagerConfiguration get() { return GlobalConfiguration.all().getInstance(ArtifactManagerConfiguration.class); @@ -46,10 +47,6 @@ public class ArtifactManagerConfiguration extends GlobalConfiguration { private final DescribableList artifactManagerFactories = new DescribableList(this); - public ArtifactManagerConfiguration() { - load(); - } - private Object readResolve() { artifactManagerFactories.setOwner(this); return this; diff --git a/core/src/main/java/jenkins/model/DownloadSettings.java b/core/src/main/java/jenkins/model/DownloadSettings.java index 8c8b75b90b..6f5e6aeabb 100644 --- a/core/src/main/java/jenkins/model/DownloadSettings.java +++ b/core/src/main/java/jenkins/model/DownloadSettings.java @@ -30,6 +30,7 @@ import hudson.model.AdministrativeMonitor; import hudson.model.AsyncPeriodicWork; import hudson.model.DownloadService; import hudson.model.DownloadService.Downloadable; +import hudson.model.PersistentDescriptor; import hudson.model.TaskListener; import hudson.model.UpdateSite; import hudson.util.FormValidation; @@ -51,7 +52,7 @@ import javax.annotation.Nonnull; */ @Restricted(NoExternalUse.class) // no clear reason for this to be an API @Extension @Symbol("downloadSettings") -public final class DownloadSettings extends GlobalConfiguration { +public final class DownloadSettings extends GlobalConfiguration implements PersistentDescriptor { public static @Nonnull DownloadSettings get() { return GlobalConfiguration.all().getInstance(DownloadSettings.class); @@ -59,10 +60,6 @@ public final class DownloadSettings extends GlobalConfiguration { private boolean useBrowser = false; - public DownloadSettings() { - load(); - } - public boolean isUseBrowser() { return useBrowser; } diff --git a/core/src/main/java/jenkins/model/JenkinsLocationConfiguration.java b/core/src/main/java/jenkins/model/JenkinsLocationConfiguration.java index ed402938b4..1e9213df75 100644 --- a/core/src/main/java/jenkins/model/JenkinsLocationConfiguration.java +++ b/core/src/main/java/jenkins/model/JenkinsLocationConfiguration.java @@ -3,6 +3,7 @@ package jenkins.model; import hudson.Extension; import hudson.Util; import hudson.XmlFile; +import hudson.model.PersistentDescriptor; import hudson.util.FormValidation; import hudson.util.XStream2; import org.jenkinsci.Symbol; @@ -31,7 +32,7 @@ import javax.annotation.Nonnull; * @since 1.494 */ @Extension @Symbol("location") -public class JenkinsLocationConfiguration extends GlobalConfiguration { +public class JenkinsLocationConfiguration extends GlobalConfiguration implements PersistentDescriptor { /** * @deprecated replaced by {@link #jenkinsUrl} */ @@ -59,10 +60,6 @@ public class JenkinsLocationConfiguration extends GlobalConfiguration { return config; } - public JenkinsLocationConfiguration() { - load(); - } - @Override public synchronized void load() { // for backward compatibility, if we don't have our own data yet, then diff --git a/core/src/main/java/jenkins/mvn/GlobalMavenConfig.java b/core/src/main/java/jenkins/mvn/GlobalMavenConfig.java index 24cdb171e2..477aa34afd 100644 --- a/core/src/main/java/jenkins/mvn/GlobalMavenConfig.java +++ b/core/src/main/java/jenkins/mvn/GlobalMavenConfig.java @@ -1,6 +1,7 @@ package jenkins.mvn; import hudson.Extension; +import hudson.model.PersistentDescriptor; import jenkins.model.GlobalConfiguration; import jenkins.model.GlobalConfigurationCategory; import jenkins.tools.ToolConfigurationCategory; @@ -11,14 +12,10 @@ import javax.annotation.Nonnull; //as close as it gets to the global Maven Project configuration @Extension(ordinal = 50) @Symbol("maven") -public class GlobalMavenConfig extends GlobalConfiguration { +public class GlobalMavenConfig extends GlobalConfiguration implements PersistentDescriptor { private SettingsProvider settingsProvider; private GlobalSettingsProvider globalSettingsProvider; - public GlobalMavenConfig() { - load(); - } - @Override public @Nonnull ToolConfigurationCategory getCategory() { return GlobalConfigurationCategory.get(ToolConfigurationCategory.class); diff --git a/core/src/main/java/jenkins/security/QueueItemAuthenticatorConfiguration.java b/core/src/main/java/jenkins/security/QueueItemAuthenticatorConfiguration.java index df85943f26..c6283401b2 100644 --- a/core/src/main/java/jenkins/security/QueueItemAuthenticatorConfiguration.java +++ b/core/src/main/java/jenkins/security/QueueItemAuthenticatorConfiguration.java @@ -1,6 +1,7 @@ package jenkins.security; import hudson.Extension; +import hudson.model.PersistentDescriptor; import hudson.model.queue.Tasks; import hudson.util.DescribableList; import jenkins.model.GlobalConfiguration; @@ -21,14 +22,10 @@ import java.util.List; * @since 1.520 */ @Extension @Symbol("queueItemAuthenticator") -public class QueueItemAuthenticatorConfiguration extends GlobalConfiguration { +public class QueueItemAuthenticatorConfiguration extends GlobalConfiguration implements PersistentDescriptor { private final DescribableList authenticators = new DescribableList(this); - public QueueItemAuthenticatorConfiguration() { - load(); - } - private Object readResolve() { authenticators.setOwner(this); return this; diff --git a/core/src/main/java/jenkins/security/UpdateSiteWarningsConfiguration.java b/core/src/main/java/jenkins/security/UpdateSiteWarningsConfiguration.java index 8ea916bab4..a623702258 100644 --- a/core/src/main/java/jenkins/security/UpdateSiteWarningsConfiguration.java +++ b/core/src/main/java/jenkins/security/UpdateSiteWarningsConfiguration.java @@ -26,6 +26,7 @@ package jenkins.security; import hudson.Extension; import hudson.PluginWrapper; +import hudson.model.PersistentDescriptor; import hudson.model.UpdateSite; import jenkins.model.GlobalConfiguration; import jenkins.model.GlobalConfigurationCategory; @@ -50,7 +51,7 @@ import java.util.Set; */ @Extension @Restricted(NoExternalUse.class) -public class UpdateSiteWarningsConfiguration extends GlobalConfiguration { +public class UpdateSiteWarningsConfiguration extends GlobalConfiguration implements PersistentDescriptor { private HashSet ignoredWarnings = new HashSet<>(); @@ -59,10 +60,6 @@ public class UpdateSiteWarningsConfiguration extends GlobalConfiguration { return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class); } - public UpdateSiteWarningsConfiguration() { - load(); - } - @Nonnull public Set getIgnoredWarnings() { return Collections.unmodifiableSet(ignoredWarnings); diff --git a/core/src/main/java/jenkins/security/apitoken/ApiTokenPropertyConfiguration.java b/core/src/main/java/jenkins/security/apitoken/ApiTokenPropertyConfiguration.java index ba7c866d73..e032f2818d 100644 --- a/core/src/main/java/jenkins/security/apitoken/ApiTokenPropertyConfiguration.java +++ b/core/src/main/java/jenkins/security/apitoken/ApiTokenPropertyConfiguration.java @@ -24,6 +24,7 @@ package jenkins.security.apitoken; import hudson.Extension; +import hudson.model.PersistentDescriptor; import jenkins.model.GlobalConfiguration; import jenkins.model.GlobalConfigurationCategory; import org.jenkinsci.Symbol; @@ -35,7 +36,7 @@ import org.jenkinsci.Symbol; */ @Extension @Symbol("apiToken") -public class ApiTokenPropertyConfiguration extends GlobalConfiguration { +public class ApiTokenPropertyConfiguration extends GlobalConfiguration implements PersistentDescriptor { /** * When a user is created, this property determines whether or not we create a legacy token for the user. * For security reasons, we do not recommend you enable this but we left that open to ease upgrades. @@ -58,10 +59,6 @@ public class ApiTokenPropertyConfiguration extends GlobalConfiguration { return GlobalConfiguration.all().get(ApiTokenPropertyConfiguration.class); } - public ApiTokenPropertyConfiguration() { - load(); - } - public boolean hasExistingConfigFile(){ return getConfigFile().exists(); }