diff --git a/core/src/main/java/hudson/model/Job.java b/core/src/main/java/hudson/model/Job.java index c40eee681de3..ab32752704be 100644 --- a/core/src/main/java/hudson/model/Job.java +++ b/core/src/main/java/hudson/model/Job.java @@ -92,6 +92,7 @@ import jenkins.model.Jenkins; import jenkins.model.JenkinsLocationConfiguration; import jenkins.model.ModelObjectWithChildren; +import jenkins.model.PeepholePermalink; import jenkins.model.ProjectNamingStrategy; import jenkins.model.RunIdMigrator; import jenkins.model.lazy.LazyBuildMixIn; @@ -941,7 +942,7 @@ public RunT getFirstBuild() { @Exported @QuickSilver public RunT getLastSuccessfulBuild() { - return (RunT) Permalink.LAST_SUCCESSFUL_BUILD.resolve(this); + return (RunT) PeepholePermalink.LAST_SUCCESSFUL_BUILD.resolve(this); } /** @@ -951,7 +952,7 @@ public RunT getLastSuccessfulBuild() { @Exported @QuickSilver public RunT getLastUnsuccessfulBuild() { - return (RunT) Permalink.LAST_UNSUCCESSFUL_BUILD.resolve(this); + return (RunT) PeepholePermalink.LAST_UNSUCCESSFUL_BUILD.resolve(this); } /** @@ -961,7 +962,7 @@ public RunT getLastUnsuccessfulBuild() { @Exported @QuickSilver public RunT getLastUnstableBuild() { - return (RunT) Permalink.LAST_UNSTABLE_BUILD.resolve(this); + return (RunT) PeepholePermalink.LAST_UNSTABLE_BUILD.resolve(this); } /** @@ -971,7 +972,7 @@ public RunT getLastUnstableBuild() { @Exported @QuickSilver public RunT getLastStableBuild() { - return (RunT) Permalink.LAST_STABLE_BUILD.resolve(this); + return (RunT) PeepholePermalink.LAST_STABLE_BUILD.resolve(this); } /** @@ -980,7 +981,7 @@ public RunT getLastStableBuild() { @Exported @QuickSilver public RunT getLastFailedBuild() { - return (RunT) Permalink.LAST_FAILED_BUILD.resolve(this); + return (RunT) PeepholePermalink.LAST_FAILED_BUILD.resolve(this); } /** @@ -989,7 +990,7 @@ public RunT getLastFailedBuild() { @Exported @QuickSilver public RunT getLastCompletedBuild() { - return (RunT) Permalink.LAST_COMPLETED_BUILD.resolve(this); + return (RunT) PeepholePermalink.LAST_COMPLETED_BUILD.resolve(this); } /** @@ -1067,6 +1068,7 @@ public long getEstimatedDuration() { * @return never null */ public PermalinkList getPermalinks() { + PeepholePermalink.initialized(); // TODO: shall we cache this? PermalinkList permalinks = new PermalinkList(Permalink.BUILTIN); for (PermalinkProjectAction ppa : getActions(PermalinkProjectAction.class)) { diff --git a/core/src/main/java/hudson/model/PermalinkProjectAction.java b/core/src/main/java/hudson/model/PermalinkProjectAction.java index 9013ddd5efc5..4ab5478243c3 100644 --- a/core/src/main/java/hudson/model/PermalinkProjectAction.java +++ b/core/src/main/java/hudson/model/PermalinkProjectAction.java @@ -25,7 +25,6 @@ package hudson.model; import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import jenkins.model.PeepholePermalink; @@ -111,114 +110,33 @@ public String getId() { return job.getLastBuild(); } }; - public static final Permalink LAST_STABLE_BUILD = new PeepholePermalink() { - @Override - public String getDisplayName() { - return Messages.Permalink_LastStableBuild(); - } - - @Override - public String getId() { - return "lastStableBuild"; - } - - @Override - public boolean apply(Run run) { - return !run.isBuilding() && run.getResult() == Result.SUCCESS; - } - }; - public static final Permalink LAST_SUCCESSFUL_BUILD = new PeepholePermalink() { - @Override - public String getDisplayName() { - return Messages.Permalink_LastSuccessfulBuild(); - } - - @Override - public String getId() { - return "lastSuccessfulBuild"; - } - - @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO needs triage") - @Override - public boolean apply(Run run) { - return !run.isBuilding() && run.getResult().isBetterOrEqualTo(Result.UNSTABLE); - } - }; - public static final Permalink LAST_FAILED_BUILD = new PeepholePermalink() { - @Override - public String getDisplayName() { - return Messages.Permalink_LastFailedBuild(); - } - - @Override - public String getId() { - return "lastFailedBuild"; - } - - @Override - public boolean apply(Run run) { - return !run.isBuilding() && run.getResult() == Result.FAILURE; - } - }; - public static final Permalink LAST_UNSTABLE_BUILD = new PeepholePermalink() { - @Override - public String getDisplayName() { - return Messages.Permalink_LastUnstableBuild(); - } + /** @deprecated use {@link PeepholePermalink#LAST_STABLE_BUILD} */ + @Deprecated + public static Permalink LAST_STABLE_BUILD; - @Override - public String getId() { - return "lastUnstableBuild"; - } + /** @deprecated use {@link PeepholePermalink#LAST_SUCCESSFUL_BUILD} */ + @Deprecated + public static Permalink LAST_SUCCESSFUL_BUILD; - @Override - public boolean apply(Run run) { - return !run.isBuilding() && run.getResult() == Result.UNSTABLE; - } - }; + /** @deprecated use {@link PeepholePermalink#LAST_FAILED_BUILD} */ + @Deprecated + public static Permalink LAST_FAILED_BUILD; - public static final Permalink LAST_UNSUCCESSFUL_BUILD = new PeepholePermalink() { - @Override - public String getDisplayName() { - return Messages.Permalink_LastUnsuccessfulBuild(); - } + /** @deprecated use {@link PeepholePermalink#LAST_UNSTABLE_BUILD} */ + @Deprecated + public static Permalink LAST_UNSTABLE_BUILD; - @Override - public String getId() { - return "lastUnsuccessfulBuild"; - } - - @Override - public boolean apply(Run run) { - return !run.isBuilding() && run.getResult() != Result.SUCCESS; - } - }; - public static final Permalink LAST_COMPLETED_BUILD = new PeepholePermalink() { - @Override - public String getDisplayName() { - return Messages.Permalink_LastCompletedBuild(); - } + /** @deprecated use {@link PeepholePermalink#LAST_UNSUCCESSFUL_BUILD} */ + @Deprecated + public static Permalink LAST_UNSUCCESSFUL_BUILD; - @Override - public String getId() { - return "lastCompletedBuild"; - } - - @Override - public boolean apply(Run run) { - return !run.isBuilding(); - } - }; + /** @deprecated use {@link PeepholePermalink#LAST_COMPLETED_BUILD} */ + @Deprecated + public static Permalink LAST_COMPLETED_BUILD; static { BUILTIN.add(LAST_BUILD); - BUILTIN.add(LAST_STABLE_BUILD); - BUILTIN.add(LAST_SUCCESSFUL_BUILD); - BUILTIN.add(LAST_FAILED_BUILD); - BUILTIN.add(LAST_UNSTABLE_BUILD); - BUILTIN.add(LAST_UNSUCCESSFUL_BUILD); - BUILTIN.add(LAST_COMPLETED_BUILD); } } } diff --git a/core/src/main/java/jenkins/model/PeepholePermalink.java b/core/src/main/java/jenkins/model/PeepholePermalink.java index 256dc438d4ae..40297b6e406c 100644 --- a/core/src/main/java/jenkins/model/PeepholePermalink.java +++ b/core/src/main/java/jenkins/model/PeepholePermalink.java @@ -2,10 +2,12 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Extension; import hudson.Util; import hudson.model.Job; import hudson.model.PermalinkProjectAction.Permalink; +import hudson.model.Result; import hudson.model.Run; import hudson.model.TaskListener; import hudson.model.listeners.RunListener; @@ -21,6 +23,8 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Convenient base implementation for {@link Permalink}s that satisfy @@ -237,6 +241,145 @@ public void onCompleted(Run run, @NonNull TaskListener listener) { } } + /** + * @since TODO + */ + public static final Permalink LAST_STABLE_BUILD = new PeepholePermalink() { + @Override + public String getDisplayName() { + return hudson.model.Messages.Permalink_LastStableBuild(); + } + + @Override + public String getId() { + return "lastStableBuild"; + } + + @Override + public boolean apply(Run run) { + return !run.isBuilding() && run.getResult() == Result.SUCCESS; + } + }; + + /** + * @since TODO + */ + public static final Permalink LAST_SUCCESSFUL_BUILD = new PeepholePermalink() { + @Override + public String getDisplayName() { + return hudson.model.Messages.Permalink_LastSuccessfulBuild(); + } + + @Override + public String getId() { + return "lastSuccessfulBuild"; + } + + @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "TODO needs triage") + @Override + public boolean apply(Run run) { + return !run.isBuilding() && run.getResult().isBetterOrEqualTo(Result.UNSTABLE); + } + }; + + /** + * @since TODO + */ + public static final Permalink LAST_FAILED_BUILD = new PeepholePermalink() { + @Override + public String getDisplayName() { + return hudson.model.Messages.Permalink_LastFailedBuild(); + } + + @Override + public String getId() { + return "lastFailedBuild"; + } + + @Override + public boolean apply(Run run) { + return !run.isBuilding() && run.getResult() == Result.FAILURE; + } + }; + + /** + * @since TODO + */ + public static final Permalink LAST_UNSTABLE_BUILD = new PeepholePermalink() { + @Override + public String getDisplayName() { + return hudson.model.Messages.Permalink_LastUnstableBuild(); + } + + @Override + public String getId() { + return "lastUnstableBuild"; + } + + @Override + public boolean apply(Run run) { + return !run.isBuilding() && run.getResult() == Result.UNSTABLE; + } + }; + + /** + * @since TODO + */ + public static final Permalink LAST_UNSUCCESSFUL_BUILD = new PeepholePermalink() { + @Override + public String getDisplayName() { + return hudson.model.Messages.Permalink_LastUnsuccessfulBuild(); + } + + @Override + public String getId() { + return "lastUnsuccessfulBuild"; + } + + @Override + public boolean apply(Run run) { + return !run.isBuilding() && run.getResult() != Result.SUCCESS; + } + }; + + /** + * @since TODO + */ + public static final Permalink LAST_COMPLETED_BUILD = new PeepholePermalink() { + @Override + public String getDisplayName() { + return hudson.model.Messages.Permalink_LastCompletedBuild(); + } + + @Override + public String getId() { + return "lastCompletedBuild"; + } + + @Override + public boolean apply(Run run) { + return !run.isBuilding(); + } + }; + + static { + BUILTIN.add(LAST_STABLE_BUILD); + BUILTIN.add(LAST_SUCCESSFUL_BUILD); + BUILTIN.add(LAST_FAILED_BUILD); + BUILTIN.add(LAST_UNSTABLE_BUILD); + BUILTIN.add(LAST_UNSUCCESSFUL_BUILD); + BUILTIN.add(LAST_COMPLETED_BUILD); + Permalink.LAST_STABLE_BUILD = LAST_STABLE_BUILD; + Permalink.LAST_SUCCESSFUL_BUILD = LAST_SUCCESSFUL_BUILD; + Permalink.LAST_FAILED_BUILD = LAST_FAILED_BUILD; + Permalink.LAST_UNSTABLE_BUILD = LAST_UNSTABLE_BUILD; + Permalink.LAST_UNSUCCESSFUL_BUILD = LAST_UNSUCCESSFUL_BUILD; + Permalink.LAST_COMPLETED_BUILD = LAST_COMPLETED_BUILD; + } + + @Restricted(NoExternalUse.class) + public static void initialized() {} + private static final int RESOLVES_TO_NONE = -1; private static final Logger LOGGER = Logger.getLogger(PeepholePermalink.class.getName()); diff --git a/core/src/test/java/jenkins/model/PeepholePermalinkTest.java b/core/src/test/java/jenkins/model/PeepholePermalinkTest.java new file mode 100644 index 000000000000..8a2cb059b230 --- /dev/null +++ b/core/src/test/java/jenkins/model/PeepholePermalinkTest.java @@ -0,0 +1,65 @@ +/* + * The MIT License + * + * Copyright 2023 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; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; + +import hudson.model.PermalinkProjectAction; +import hudson.model.Run; +import java.util.stream.Collectors; +import org.junit.Test; + +public final class PeepholePermalinkTest { + + @Test + public void classLoadingDeadlock() throws Exception { + PeepholePermalink.initialized(); + Thread t = new Thread(() -> { + assertThat("successfully loaded permalinks", + PermalinkProjectAction.Permalink.BUILTIN.stream().map(p -> p.getId()).collect(Collectors.toSet()), + containsInAnyOrder("lastBuild", "lastStableBuild", "lastSuccessfulBuild", "lastFailedBuild", "lastUnstableBuild", "lastUnsuccessfulBuild", "lastCompletedBuild")); + }); + t.start(); + new PeepholePermalink() { + @Override + public boolean apply(Run run) { + throw new UnsupportedOperationException(); + } + + @Override + public String getDisplayName() { + throw new UnsupportedOperationException(); + } + + @Override + public String getId() { + throw new UnsupportedOperationException(); + } + }; + t.join(); + } + +}