From 857056956e7625026bf576900a51c1299cfbc141 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Sun, 21 Nov 2021 19:28:18 -0800 Subject: [PATCH 01/10] `Stream`/`Spliterator` support for `RunMap`/`RunList` --- .../java/hudson/model/AbstractProject.java | 2 +- core/src/main/java/hudson/model/RSS.java | 2 +- core/src/main/java/hudson/util/RunList.java | 16 ++ .../model/lazy/AbstractLazyLoadRunMap.java | 146 ++++++++++++++++++ .../model/lazy/LazyLoadRunMapEntrySet.java | 8 + .../test/java/hudson/model/RunMapTest.java | 78 ++++++++++ 6 files changed, 250 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index 067eef29b26e..3d6ceecb92ba 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -1657,7 +1657,7 @@ public SortedMap getRelationship(AbstractProject that) { * * For each given build, find the build number range of the given project and put that into the map. */ - private void checkAndRecord(AbstractProject that, TreeMap r, Collection builds) { + private void checkAndRecord(AbstractProject that, TreeMap r, Iterable builds) { for (R build : builds) { RangeSet rs = build.getDownstreamRelationship(that); if(rs==null || rs.isEmpty()) diff --git a/core/src/main/java/hudson/model/RSS.java b/core/src/main/java/hudson/model/RSS.java index 4b95c28d937f..08283ad31c4c 100644 --- a/core/src/main/java/hudson/model/RSS.java +++ b/core/src/main/java/hudson/model/RSS.java @@ -52,7 +52,7 @@ public final class RSS { * @param adapter * Controls how to render entries to RSS. */ - public static void forwardToRss(String title, String url, Collection entries, FeedAdapter adapter, StaplerRequest req, HttpServletResponse rsp) throws IOException, ServletException { + public static void forwardToRss(String title, String url, Iterable entries, FeedAdapter adapter, StaplerRequest req, HttpServletResponse rsp) throws IOException, ServletException { req.setAttribute("adapter",adapter); req.setAttribute("title",title); req.setAttribute("url",url); diff --git a/core/src/main/java/hudson/util/RunList.java b/core/src/main/java/hudson/util/RunList.java index 37daa7c67ba0..54a28d63dc8c 100644 --- a/core/src/main/java/hudson/util/RunList.java +++ b/core/src/main/java/hudson/util/RunList.java @@ -45,7 +45,9 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.Spliterator; import java.util.function.Predicate; +import java.util.stream.Stream; /** * {@link List} of {@link Run}s, sorted in the descending date order. @@ -167,6 +169,20 @@ public List subList(int fromIndex, int toIndex) { return r; } + @Override + public Spliterator spliterator() { + return base.spliterator(); + } + + @Override + public Stream stream() { + if (base instanceof Collection) { + return ((Collection) base).stream(); + } else { + return super.stream(); + } + } + @Override public int indexOf(Object o) { int index=0; diff --git a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java index e41b7e68b6af..838692716d7f 100644 --- a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java +++ b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java @@ -32,15 +32,22 @@ import hudson.model.RunMap; 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.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.logging.Level; import java.util.logging.Logger; import jenkins.util.MemoryReductionUtil; @@ -99,6 +106,145 @@ public abstract class AbstractLazyLoadRunMap extends AbstractMap i private volatile Index index = new Index(); private LazyLoadRunMapEntrySet entrySet = new LazyLoadRunMapEntrySet<>(this); + private transient Set keySet; + private transient Collection values; + + @Override + public Set keySet() { + Set ks = keySet; + if (ks == null) { + ks = new AbstractSet() { + @Override + public Iterator iterator() { + return new Iterator() { + private final Iterator> 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 spliterator() { + return new Spliterators.AbstractIntSpliterator( + Long.MAX_VALUE, + Spliterator.DISTINCT | Spliterator.ORDERED | Spliterator.SORTED) { + private final Iterator 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 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; + } + + @Override + public Collection values() { + Collection vals = values; + if (vals == null) { + vals = new AbstractCollection() { + @Override + public Iterator iterator() { + return new Iterator() { + private final Iterator> 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 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; + } + /** * Historical holder for map. * diff --git a/core/src/main/java/jenkins/model/lazy/LazyLoadRunMapEntrySet.java b/core/src/main/java/jenkins/model/lazy/LazyLoadRunMapEntrySet.java index bdb714817535..6d238da14a4c 100644 --- a/core/src/main/java/jenkins/model/lazy/LazyLoadRunMapEntrySet.java +++ b/core/src/main/java/jenkins/model/lazy/LazyLoadRunMapEntrySet.java @@ -6,6 +6,8 @@ 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; /** @@ -91,6 +93,12 @@ public void remove() { }; } + @Override + public Spliterator> spliterator() { + return Spliterators.spliteratorUnknownSize( + iterator(), Spliterator.DISTINCT | Spliterator.ORDERED); + } + @Override public Object[] toArray() { return all().toArray(); diff --git a/test/src/test/java/hudson/model/RunMapTest.java b/test/src/test/java/hudson/model/RunMapTest.java index 833dc3c51b95..4d1bc00cd5b6 100644 --- a/test/src/test/java/hudson/model/RunMapTest.java +++ b/test/src/test/java/hudson/model/RunMapTest.java @@ -6,15 +6,25 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import com.google.common.collect.Streams; import hudson.model.queue.QueueTaskFuture; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.SortedMap; +import java.util.Spliterator; +import java.util.TreeMap; import javax.xml.transform.Source; import javax.xml.transform.stream.StreamSource; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.RunLoadCounter; import org.jvnet.hudson.test.SleepBuilder; public class RunMapTest { @@ -130,4 +140,72 @@ private Object readResolve() { assertEquals(0, runs.size()); } + @Test + public void stream() throws Exception { + FreeStyleProject p = r.createFreeStyleProject(); + SortedMap builds = new TreeMap<>(Collections.reverseOrder()); + for (int i = 0; i < 10; i++) { + FreeStyleBuild build = r.buildAndAssertSuccess(p); + builds.put(build.number, build); + } + RunMap runMap = p._getRuns(); + + assertEquals(builds.size(), runMap.entrySet().size()); + assertTrue( + runMap.entrySet().stream().spliterator().hasCharacteristics(Spliterator.DISTINCT)); + assertTrue( + runMap.entrySet().stream().spliterator().hasCharacteristics(Spliterator.ORDERED)); + assertFalse(runMap.entrySet().stream().spliterator().hasCharacteristics(Spliterator.SIZED)); + assertFalse( + runMap.entrySet().stream().spliterator().hasCharacteristics(Spliterator.SORTED)); + assertThrows( + IllegalStateException.class, + () -> runMap.entrySet().stream().spliterator().getComparator()); + + assertEquals(new ArrayList<>(builds.keySet()), new ArrayList<>(runMap.keySet())); + Comparator comparator = + runMap.keySet().stream().spliterator().getComparator(); + assertNotNull(comparator); + List origOrder = new ArrayList<>(builds.keySet()); + List sorted = new ArrayList<>(origOrder); + sorted.sort(comparator); + assertEquals(origOrder, sorted); + assertTrue(runMap.keySet().stream().spliterator().hasCharacteristics(Spliterator.DISTINCT)); + assertTrue(runMap.keySet().stream().spliterator().hasCharacteristics(Spliterator.ORDERED)); + assertFalse(runMap.keySet().stream().spliterator().hasCharacteristics(Spliterator.SIZED)); + assertTrue(runMap.keySet().stream().spliterator().hasCharacteristics(Spliterator.SORTED)); + + assertEquals(new ArrayList<>(builds.values()), new ArrayList<>(runMap.values())); + assertTrue(runMap.values().stream().spliterator().hasCharacteristics(Spliterator.DISTINCT)); + assertTrue(runMap.values().stream().spliterator().hasCharacteristics(Spliterator.ORDERED)); + assertFalse(runMap.values().stream().spliterator().hasCharacteristics(Spliterator.SIZED)); + assertFalse(runMap.values().stream().spliterator().hasCharacteristics(Spliterator.SORTED)); + assertThrows( + IllegalStateException.class, + () -> runMap.values().stream().spliterator().getComparator()); + } + + @Test + public void runLoadCounterFirst() throws Exception { + FreeStyleProject p = r.createFreeStyleProject(); + RunLoadCounter.prepare(p); + for (int i = 0; i < 10; i++) { + r.buildAndAssertSuccess(p); + } + assertEquals( + 10, + RunLoadCounter.assertMaxLoads(p, 2, () -> p.getBuilds().stream().findFirst().orElse(null).number).intValue()); + } + + @Test + public void runLoadCounterLimit() throws Exception { + FreeStyleProject p = r.createFreeStyleProject(); + RunLoadCounter.prepare(p); + for (int i = 0; i < 10; i++) { + r.buildAndAssertSuccess(p); + } + assertEquals( + 6, + RunLoadCounter.assertMaxLoads(p, 6, () -> Streams.findLast(p.getBuilds().stream().limit(5)).orElse(null).number).intValue()); + } } From 04870bb51d1cdc34378c1b3b6635643439c07595 Mon Sep 17 00:00:00 2001 From: benebsiny Date: Mon, 22 Nov 2021 22:02:16 +0800 Subject: [PATCH 02/10] [JENKINS-67193] "View as plain text" from polling log is behind top bar --- core/src/main/resources/lib/layout/rightspace.jelly | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/layout/rightspace.jelly b/core/src/main/resources/lib/layout/rightspace.jelly index 31f9257b674f..3e38e345d2a0 100644 --- a/core/src/main/resources/lib/layout/rightspace.jelly +++ b/core/src/main/resources/lib/layout/rightspace.jelly @@ -28,7 +28,7 @@ THE SOFTWARE. Creates a space for the right-hand side of the page. This sticks to the right of the page even when the content overflows. -
+
\ No newline at end of file From 55907ff611f5634b647786ace0173ffc9922cc1f Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Mon, 22 Nov 2021 09:39:45 -0800 Subject: [PATCH 03/10] Revert unnecessary breaking change to public API --- core/src/main/java/hudson/model/RSS.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/model/RSS.java b/core/src/main/java/hudson/model/RSS.java index 08283ad31c4c..4b95c28d937f 100644 --- a/core/src/main/java/hudson/model/RSS.java +++ b/core/src/main/java/hudson/model/RSS.java @@ -52,7 +52,7 @@ public final class RSS { * @param adapter * Controls how to render entries to RSS. */ - public static void forwardToRss(String title, String url, Iterable entries, FeedAdapter adapter, StaplerRequest req, HttpServletResponse rsp) throws IOException, ServletException { + public static void forwardToRss(String title, String url, Collection entries, FeedAdapter adapter, StaplerRequest req, HttpServletResponse rsp) throws IOException, ServletException { req.setAttribute("adapter",adapter); req.setAttribute("title",title); req.setAttribute("url",url); From 400ee9d395d60ef13fe6be5dd3327e9050c5cb6f Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Mon, 22 Nov 2021 09:40:01 -0800 Subject: [PATCH 04/10] Remove unnecessary overload --- core/src/main/java/hudson/util/RunList.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/core/src/main/java/hudson/util/RunList.java b/core/src/main/java/hudson/util/RunList.java index 54a28d63dc8c..04e2ceb10a86 100644 --- a/core/src/main/java/hudson/util/RunList.java +++ b/core/src/main/java/hudson/util/RunList.java @@ -47,7 +47,6 @@ import java.util.Set; import java.util.Spliterator; import java.util.function.Predicate; -import java.util.stream.Stream; /** * {@link List} of {@link Run}s, sorted in the descending date order. @@ -174,15 +173,6 @@ public Spliterator spliterator() { return base.spliterator(); } - @Override - public Stream stream() { - if (base instanceof Collection) { - return ((Collection) base).stream(); - } else { - return super.stream(); - } - } - @Override public int indexOf(Object o) { int index=0; From e8487c9811261a84bf2a2bbe3fe7d3abfdd602a7 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Mon, 22 Nov 2021 13:00:18 -0800 Subject: [PATCH 05/10] Update core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java Co-authored-by: Jesse Glick --- .../main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java index 838692716d7f..24be3366bf3c 100644 --- a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java +++ b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java @@ -106,8 +106,8 @@ public abstract class AbstractLazyLoadRunMap extends AbstractMap i private volatile Index index = new Index(); private LazyLoadRunMapEntrySet entrySet = new LazyLoadRunMapEntrySet<>(this); - private transient Set keySet; - private transient Collection values; + private transient volatile Set keySet; + private transient volatile Collection values; @Override public Set keySet() { From ca67a716388a326e2af4477dc001464bd6da8f96 Mon Sep 17 00:00:00 2001 From: Tim Jacomb <21194782+timja@users.noreply.github.com> Date: Thu, 25 Nov 2021 20:18:28 +0000 Subject: [PATCH 06/10] JENKINS-61278 JCasC support for managing log recorders (#4538) Co-authored-by: Joseph Petersen Co-authored-by: Oleg Nenashev --- .../main/java/hudson/logging/LogRecorder.java | 161 ++++++++++++++---- .../hudson/logging/LogRecorderManager.java | 53 ++++-- core/src/main/java/jenkins/model/Jenkins.java | 14 +- .../logging/LogRecorder/configure.jelly | 2 +- .../logging/LogRecorderManager/index.jelly | 6 +- .../java/hudson/logging/LogRecorderTest.java | 8 +- .../logging/LogRecorderManagerTest.java | 57 ++++++- 7 files changed, 237 insertions(+), 64 deletions(-) diff --git a/core/src/main/java/hudson/logging/LogRecorder.java b/core/src/main/java/hudson/logging/LogRecorder.java index 204827cb399f..ab82ffdd5550 100644 --- a/core/src/main/java/hudson/logging/LogRecorder.java +++ b/core/src/main/java/hudson/logging/LogRecorder.java @@ -1,18 +1,18 @@ /* * The MIT License - * + * * Copyright (c) 2004-2009, 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 * 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 @@ -28,8 +28,14 @@ import hudson.BulkChange; import hudson.Extension; import hudson.FilePath; +import hudson.RestrictedSince; import hudson.Util; import hudson.XmlFile; +import hudson.util.CopyOnWriteList; +import hudson.util.HttpResponses; +import java.util.Objects; +import jenkins.util.MemoryReductionUtil; +import jenkins.model.Jenkins; import hudson.model.AbstractModelObject; import hudson.model.AutoCompletionCandidates; import hudson.model.Computer; @@ -39,8 +45,6 @@ import hudson.remoting.Channel; import hudson.remoting.VirtualChannel; import hudson.slaves.ComputerListener; -import hudson.util.CopyOnWriteList; -import hudson.util.HttpResponses; import hudson.util.RingBufferLogHandler; import hudson.util.XStream2; import java.io.File; @@ -66,9 +70,7 @@ import java.util.logging.LogRecord; import java.util.logging.Logger; import javax.servlet.ServletException; -import jenkins.model.Jenkins; import jenkins.security.MasterToSlaveCallable; -import jenkins.util.MemoryReductionUtil; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.kohsuke.accmod.Restricted; @@ -97,13 +99,54 @@ public class LogRecorder extends AbstractModelObject implements Saveable { private volatile String name; - public final CopyOnWriteList targets = new CopyOnWriteList<>(); + @Deprecated + @Restricted(NoExternalUse.class) + @RestrictedSince("TODO") + /** + * No longer used. + * + * @deprecated use {@link #getLoggers()} + */ + public final transient CopyOnWriteList targets = new CopyOnWriteList<>(); + private List loggers = new ArrayList<>(); private static final TargetComparator TARGET_COMPARATOR = new TargetComparator(); - + + @DataBoundConstructor + public LogRecorder(String name) { + this.name = name; + // register it only once when constructed, and when this object dies + // WeakLogHandler will remove it + new WeakLogHandler(handler,Logger.getLogger("")); + } + + private Object readResolve() { + if (loggers == null) { + loggers = new ArrayList<>(); + } + + List tempLoggers = new ArrayList<>(loggers); + + if (!targets.isEmpty()) { + loggers.addAll(targets.getView()); + } + if (!tempLoggers.isEmpty() && !targets.getView().equals(tempLoggers)) { + targets.addAll(tempLoggers); + } + return this; + } + + public List getLoggers() { + return loggers; + } + + public void setLoggers(List loggers) { + this.loggers = loggers; + } + @Restricted(NoExternalUse.class) Target[] orderedTargets() { // will contain targets ordered by reverse name length (place specific targets at the beginning) - Target[] ts = targets.toArray(new Target[]{}); + Target[] ts = loggers.toArray(new Target[]{}); Arrays.sort(ts, TARGET_COMPARATOR); @@ -220,6 +263,23 @@ public String getName() { return name; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Target target = (Target) o; + return level == target.level && Objects.equals(name, target.name); + } + + @Override + public int hashCode() { + return Objects.hash(name, level); + } + @Deprecated public boolean includes(LogRecord r) { if(r.getLevel().intValue() < level) @@ -272,7 +332,7 @@ public void disable() { } } - + private static class TargetComparator implements Comparator, Serializable { private static final long serialVersionUID = 9285340752515798L; @@ -316,21 +376,14 @@ void broadcast() { @Extension @Restricted(NoExternalUse.class) public static final class ComputerLogInitializer extends ComputerListener { @Override public void preOnline(Computer c, Channel channel, FilePath root, TaskListener listener) throws IOException, InterruptedException { - for (LogRecorder recorder : Jenkins.get().getLog().logRecorders.values()) { - for (Target t : recorder.targets) { + for (LogRecorder recorder : Jenkins.get().getLog().getRecorders()) { + for (Target t : recorder.getLoggers()) { channel.call(new SetLevel(t.name, t.getLevel())); } } } } - public LogRecorder(String name) { - this.name = name; - // register it only once when constructed, and when this object dies - // WeakLogHandler will remove it - new WeakLogHandler(handler,Logger.getLogger("")); - } - @Override public String getDisplayName() { return name; @@ -364,16 +417,16 @@ public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp Jenkins.checkGoodName(newName); oldFile = getConfigFile(); // rename - getParent().logRecorders.remove(name); + List recorders = getParent().getRecorders(); + recorders.remove(new LogRecorder(name)); this.name = newName; - getParent().logRecorders.put(name,this); + recorders.add(this); + getParent().setRecorders(recorders); // ensure that legacy logRecorders field is synced on save redirect = "../" + Util.rawEncode(newName) + '/'; } - List newTargets = req.bindJSONToList(Target.class, src.get("targets")); - for (Target t : newTargets) - t.enable(); - targets.replaceBy(newTargets); + List newTargets = req.bindJSONToList(Target.class, src.get("loggers")); + setLoggers(newTargets); save(); if (oldFile!=null) oldFile.delete(); @@ -393,8 +446,7 @@ public HttpResponse doClear() throws IOException { */ public synchronized void load() throws IOException { getConfigFile().unmarshal(this); - for (Target t : targets) - t.enable(); + loggers.forEach(Target::enable); } /** @@ -403,10 +455,49 @@ public synchronized void load() throws IOException { @Override public synchronized void save() throws IOException { if(BulkChange.contains(this)) return; + + handlePluginUpdatingLegacyLogManagerMap(); getConfigFile().write(this); + loggers.forEach(Target::enable); + SaveableListener.fireOnChange(this, getConfigFile()); } + @SuppressWarnings("deprecation") // this is for compatibility + private void handlePluginUpdatingLegacyLogManagerMap() { + if (getParent().logRecorders.size() > getParent().getRecorders().size()) { + for (LogRecorder logRecorder : getParent().logRecorders.values()) { + if (!getParent().getRecorders().contains(logRecorder)) { + getParent().getRecorders().add(logRecorder); + } + } + } + if (getParent().getRecorders().size() > getParent().logRecorders.size()) { + for (LogRecorder logRecorder : getParent().getRecorders()) { + if (!getParent().logRecorders.containsKey(logRecorder.getName())) { + getParent().logRecorders.put(logRecorder.getName(), logRecorder); + } + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LogRecorder that = (LogRecorder) o; + return name.equals(that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + /** * Deletes this recorder, then go back to the parent. */ @@ -415,14 +506,12 @@ public synchronized void doDoDelete(StaplerResponse rsp) throws IOException, Ser Jenkins.get().checkPermission(Jenkins.ADMINISTER); getConfigFile().delete(); - getParent().logRecorders.remove(name); + getParent().getRecorders().remove(new LogRecorder(name)); // Disable logging for all our targets, // then reenable all other loggers in case any also log the same targets - for (Target t : targets) - t.disable(); - for (LogRecorder log : getParent().logRecorders.values()) - for (Target t : log.targets) - t.enable(); + loggers.forEach(Target::disable); + + getParent().getRecorders().forEach(logRecorder -> logRecorder.getLoggers().forEach(Target::enable)); rsp.sendRedirect2(".."); } @@ -468,7 +557,7 @@ public int compare(Computer c1, Computer c2) { List recs = new ArrayList<>(); try { for (LogRecord rec : c.getLogRecords()) { - for (Target t : targets) { + for (Target t : loggers) { if (t.includes(rec)) { recs.add(rec); break; diff --git a/core/src/main/java/hudson/logging/LogRecorderManager.java b/core/src/main/java/hudson/logging/LogRecorderManager.java index 84245ed132c9..cc1948d2a5d7 100644 --- a/core/src/main/java/hudson/logging/LogRecorderManager.java +++ b/core/src/main/java/hudson/logging/LogRecorderManager.java @@ -1,18 +1,18 @@ /* * The MIT License - * + * * Copyright (c) 2004-2010, 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 * 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 @@ -24,10 +24,12 @@ package hudson.logging; import static hudson.init.InitMilestone.PLUGINS_PREPARED; +import static java.util.stream.Collectors.toMap; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.FeedAdapter; import hudson.Functions; +import hudson.RestrictedSince; import hudson.init.Initializer; import hudson.model.AbstractModelObject; import hudson.model.RSS; @@ -41,6 +43,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; @@ -53,6 +56,8 @@ import org.apache.commons.io.filefilter.WildcardFileFilter; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.HttpRedirect; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.QueryParameter; @@ -68,10 +73,35 @@ */ public class LogRecorderManager extends AbstractModelObject implements ModelObjectWithChildren, StaplerProxy { /** - * {@link LogRecorder}s keyed by their {@linkplain LogRecorder#name name}. + * {@link LogRecorder}s keyed by their {@linkplain LogRecorder#getName()} name}. + * + * @deprecated use {@link #getRecorders()} instead */ + @Deprecated + @Restricted(NoExternalUse.class) + @RestrictedSince("TODO") public final transient Map logRecorders = new CopyOnWriteMap.Tree<>(); + private List recorders; + + @DataBoundConstructor + public LogRecorderManager() { + this.recorders = new ArrayList<>(); + } + + public List getRecorders() { + return recorders; + } + + @DataBoundSetter + public void setRecorders(List recorders) { + this.recorders = recorders; + + Map values = recorders.stream() + .collect(toMap(LogRecorder::getName, Function.identity())); + ((CopyOnWriteMap) logRecorders).replaceBy(values); + } + @Override public String getDisplayName() { return Messages.LogRecorderManager_DisplayName(); @@ -87,7 +117,7 @@ public LogRecorder getDynamic(String token) { } public LogRecorder getLogRecorder(String token) { - return logRecorders.get(token); + return recorders.stream().filter(logRecorder -> logRecorder.getName().equals(token)).findAny().orElse(null); } static File configDir() { @@ -98,7 +128,7 @@ static File configDir() { * Loads the configuration from disk. */ public void load() throws IOException { - logRecorders.clear(); + recorders.clear(); File dir = configDir(); File[] files = dir.listFiles((FileFilter)new WildcardFileFilter("*.xml")); if(files==null) return; @@ -107,8 +137,9 @@ public void load() throws IOException { name = name.substring(0,name.length()-4); // cut off ".xml" LogRecorder lr = new LogRecorder(name); lr.load(); - logRecorders.put(name,lr); + recorders.add(lr); } + setRecorders(recorders); // ensure that legacy logRecorders field is synced on load } /** @@ -119,8 +150,8 @@ public HttpResponse doNewLogRecorder(@QueryParameter String name) { Jenkins.get().checkPermission(Jenkins.ADMINISTER); Jenkins.checkGoodName(name); - - logRecorders.put(name,new LogRecorder(name)); + + recorders.add(new LogRecorder(name)); // redirect to the config screen return new HttpRedirect(name+"/configure"); @@ -130,7 +161,7 @@ public HttpResponse doNewLogRecorder(@QueryParameter String name) { public ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception { ContextMenu menu = new ContextMenu(); menu.add("all","All Jenkins Logs"); - for (LogRecorder lr : logRecorders.values()) { + for (LogRecorder lr : recorders) { menu.add(lr.getSearchUrl(), lr.getDisplayName()); } return menu; diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 6a46ec37db3e..b7cbd877c2bd 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -875,7 +875,8 @@ public static Jenkins getInstance() { /** * Bound to "/log". */ - private final transient LogRecorderManager log = new LogRecorderManager(); + private transient LogRecorderManager log = new LogRecorderManager(); + private final transient boolean oldJenkinsJVM; @@ -2644,6 +2645,17 @@ public LogRecorderManager getLog() { return log; } + /** + * Set the LogRecorderManager. + * + * @param log the LogRecorderManager to set + * @since TODO + */ + public void setLog(LogRecorderManager log) { + checkPermission(ADMINISTER); + this.log = log; + } + /** * A convenience method to check if there's some security * restrictions in place. diff --git a/core/src/main/resources/hudson/logging/LogRecorder/configure.jelly b/core/src/main/resources/hudson/logging/LogRecorder/configure.jelly index fc292faea2be..61aa94aa2fb4 100644 --- a/core/src/main/resources/hudson/logging/LogRecorder/configure.jelly +++ b/core/src/main/resources/hudson/logging/LogRecorder/configure.jelly @@ -47,7 +47,7 @@ THE SOFTWARE. description="${%List of loggers and the log levels to record}" help="/help/LogRecorder/logger.html"> - + - +
@@ -74,7 +74,9 @@ THE SOFTWARE.
diff --git a/core/src/main/resources/hudson/logging/LogRecorderManager/index.jelly b/core/src/main/resources/hudson/logging/LogRecorderManager/index.jelly index 97f957a5581c..f72787a64fa2 100644 --- a/core/src/main/resources/hudson/logging/LogRecorderManager/index.jelly +++ b/core/src/main/resources/hudson/logging/LogRecorderManager/index.jelly @@ -59,7 +59,7 @@ THE SOFTWARE.
- +

+ +

diff --git a/core/src/test/java/hudson/logging/LogRecorderTest.java b/core/src/test/java/hudson/logging/LogRecorderTest.java index 5e48e89effa0..63c6a185a98f 100644 --- a/core/src/test/java/hudson/logging/LogRecorderTest.java +++ b/core/src/test/java/hudson/logging/LogRecorderTest.java @@ -68,7 +68,7 @@ public class LogRecorderTest { @Test public void testClearing() throws IOException { LogRecorder lr = new LogRecorder("foo"); LogRecorder.Target t = new LogRecorder.Target("", Level.FINE); - lr.targets.add(t); + lr.getLoggers().add(t); Jenkins j = Mockito.mock(Jenkins.class); try (MockedStatic mocked = Mockito.mockStatic(Jenkins.class)) { @@ -92,9 +92,9 @@ public class LogRecorderTest { LogRecorder.Target targetLevel1 = new LogRecorder.Target("foo", Level.INFO); LogRecorder.Target targetLevel2 = new LogRecorder.Target("foo.bar", Level.SEVERE); - lr.targets.add(targetLevel1); - lr.targets.add(targetLevel2); - lr.targets.add(targetLevel0); + lr.getLoggers().add(targetLevel1); + lr.getLoggers().add(targetLevel2); + lr.getLoggers().add(targetLevel0); assertEquals(lr.orderedTargets()[0], targetLevel2); assertEquals(lr.orderedTargets()[1], targetLevel1); diff --git a/test/src/test/java/hudson/logging/LogRecorderManagerTest.java b/test/src/test/java/hudson/logging/LogRecorderManagerTest.java index 696d336a0905..e3bf412728da 100644 --- a/test/src/test/java/hudson/logging/LogRecorderManagerTest.java +++ b/test/src/test/java/hudson/logging/LogRecorderManagerTest.java @@ -1,18 +1,18 @@ /* * The MIT License - * + * * Copyright (c) 2004-2009, 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 * 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 @@ -23,6 +23,8 @@ */ package hudson.logging; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -32,6 +34,7 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage; import hudson.model.Computer; import hudson.remoting.VirtualChannel; +import java.io.IOException; import java.util.List; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -72,21 +75,21 @@ public class LogRecorderManagerTest { // TODO could also go through WebClient to assert that the config UI works LogRecorderManager mgr = j.jenkins.getLog(); LogRecorder r1 = new LogRecorder("r1"); - mgr.logRecorders.put("r1", r1); + mgr.getRecorders().add(r1); LogRecorder.Target t = new LogRecorder.Target("ns1", Level.FINE); - r1.targets.add(t); + r1.getLoggers().add(t); r1.save(); t.enable(); Computer c = j.createOnlineSlave().toComputer(); assertNotNull(c); t = new LogRecorder.Target("ns2", Level.FINER); - r1.targets.add(t); + r1.getLoggers().add(t); r1.save(); t.enable(); LogRecorder r2 = new LogRecorder("r2"); - mgr.logRecorders.put("r2", r2); + mgr.getRecorders().add(r2); t = new LogRecorder.Target("ns3", Level.FINE); - r2.targets.add(t); + r2.getLoggers().add(t); r2.save(); t.enable(); VirtualChannel ch = c.getChannel(); @@ -120,6 +123,42 @@ public class LogRecorderManagerTest { assertFalse(text, text.contains("LambdaLog @FINER")); } + @Test + @SuppressWarnings("deprecation") + public void addingLogRecorderToLegacyMapAddsToRecordersList() throws IOException { + LogRecorderManager log = j.jenkins.getLog(); + + assertThat(log.logRecorders.size(), is(0)); + assertThat(log.getRecorders().size(), is(0)); + + LogRecorder logRecorder = new LogRecorder("dummy"); + logRecorder.getLoggers().add(new LogRecorder.Target("dummy", Level.ALL)); + + log.logRecorders.put("dummy", logRecorder); + logRecorder.save(); + + assertThat(log.logRecorders.size(), is(1)); + assertThat(log.getRecorders().size(), is(1)); + } + + @Test + @SuppressWarnings("deprecation") + public void addingLogRecorderToListAddsToLegacyRecordersMap() throws IOException { + LogRecorderManager log = j.jenkins.getLog(); + + assertThat(log.logRecorders.size(), is(0)); + assertThat(log.getRecorders().size(), is(0)); + + LogRecorder logRecorder = new LogRecorder("dummy"); + logRecorder.getLoggers().add(new LogRecorder.Target("dummy", Level.ALL)); + + log.getRecorders().add(logRecorder); + logRecorder.save(); + + assertThat(log.logRecorders.size(), is(1)); + assertThat(log.getRecorders().size(), is(1)); + } + private static final class Log extends MasterToSlaveCallable { private final Level level; private final String logger; From cc1966388287e4e49f6de9cfd165afcbdd17b6da Mon Sep 17 00:00:00 2001 From: Ildefonso Montero Date: Fri, 26 Nov 2021 13:18:18 +0100 Subject: [PATCH 07/10] [JEP-234] Customizable header proposal (#5909) Co-authored-by: James Nord Co-authored-by: Jesse Glick Co-authored-by: Vincent Latombe Co-authored-by: Tim Jacomb <21194782+timja@users.noreply.github.com> --- .../main/java/jenkins/views/FullHeader.java | 16 ++++ core/src/main/java/jenkins/views/Header.java | 59 ++++++++++++++ .../java/jenkins/views/JenkinsHeader.java | 18 +++++ .../java/jenkins/views/PartialHeader.java | 49 ++++++++++++ .../views/JenkinsHeader/headerContent.jelly | 80 +++++++++++++++++++ .../resources/lib/layout/pageHeader.jelly | 80 +------------------ 6 files changed, 224 insertions(+), 78 deletions(-) create mode 100644 core/src/main/java/jenkins/views/FullHeader.java create mode 100644 core/src/main/java/jenkins/views/Header.java create mode 100644 core/src/main/java/jenkins/views/JenkinsHeader.java create mode 100644 core/src/main/java/jenkins/views/PartialHeader.java create mode 100644 core/src/main/resources/jenkins/views/JenkinsHeader/headerContent.jelly diff --git a/core/src/main/java/jenkins/views/FullHeader.java b/core/src/main/java/jenkins/views/FullHeader.java new file mode 100644 index 000000000000..aef4537bdbf0 --- /dev/null +++ b/core/src/main/java/jenkins/views/FullHeader.java @@ -0,0 +1,16 @@ +package jenkins.views; + +/** + * {@link Header} that provides its own resources as full replacement. It does not + * depends on any core resource (images, CSS, JS, etc.) + * + * Given this kind of header is totally independent, it will be compatible by default. + * + * @see Header + */ +public abstract class FullHeader extends Header { + + public boolean isCompatible() { + return true; + } +} diff --git a/core/src/main/java/jenkins/views/Header.java b/core/src/main/java/jenkins/views/Header.java new file mode 100644 index 000000000000..9f4f6ecb3d55 --- /dev/null +++ b/core/src/main/java/jenkins/views/Header.java @@ -0,0 +1,59 @@ +package jenkins.views; + +import java.util.Optional; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import hudson.ExtensionList; +import hudson.ExtensionPoint; + +/** + * Extension point that provides capabilities to render a specific header. + * + * Extend {@link PartialHeader} or {@link FullHeader} depending on the use case. + * + * The default Jenkins header is provided as an implementation of a {@link FullHeader} + * named {@link JenkinsHeader}. + * + * The first header located will be used, set the ordinal field on Extension to have a higher priority. + * + * The header content will be injected inside the {@code pageHeader.jelly}, based on the header + * retrieved by the {@link Header#get()} method. That header content will be provided + * inside a resource called {@code headerContent.jelly}. It performs a full replacement + * of the header. + * + * @see PartialHeader + * @see FullHeader + * @see JenkinsHeader + * @since TODO + */ +public abstract class Header implements ExtensionPoint { + + /** + * Checks if header is available + * @return if header is available + */ + public boolean isAvailable() { + return isCompatible() && isEnabled(); + } + + /** + * Checks API compatibility of the header + * @return if header is compatible + */ + public abstract boolean isCompatible(); + + /** + * Checks if header is enabled. + * @return if header is enabled + */ + public abstract boolean isEnabled(); + + @Restricted(NoExternalUse.class) + public static Header get() { + Optional
header = ExtensionList.lookup(Header.class).stream().filter(Header::isAvailable).findFirst(); + return header.orElseGet(() -> new JenkinsHeader()); + } + +} diff --git a/core/src/main/java/jenkins/views/JenkinsHeader.java b/core/src/main/java/jenkins/views/JenkinsHeader.java new file mode 100644 index 000000000000..6f1472d1af18 --- /dev/null +++ b/core/src/main/java/jenkins/views/JenkinsHeader.java @@ -0,0 +1,18 @@ +package jenkins.views; + +import hudson.Extension; + +/** + * Default {@link Header} provided by Jenkins + * + * @see Header + */ +@Extension(ordinal = Integer.MIN_VALUE) +public class JenkinsHeader extends FullHeader { + + @Override + public boolean isEnabled() { + return true; + } + +} diff --git a/core/src/main/java/jenkins/views/PartialHeader.java b/core/src/main/java/jenkins/views/PartialHeader.java new file mode 100644 index 000000000000..043cef2addcb --- /dev/null +++ b/core/src/main/java/jenkins/views/PartialHeader.java @@ -0,0 +1,49 @@ +package jenkins.views; + +import java.util.logging.Logger; + +import hudson.ExtensionList; +import hudson.init.InitMilestone; +import hudson.init.Initializer; +import hudson.util.AdministrativeError; +import jenkins.model.Jenkins; + +/** + * {@link Header} that relies on core resources (images, CSS, JS, etc.) to perform + * partial replacements. + * + * Given this kind of header is not independent, compatibility should be managed by the + * specific {@link Header} compatibility header version value + * + * @see Header + */ +public abstract class PartialHeader extends Header { + + private static Logger LOGGER = Logger.getLogger(PartialHeader.class.getName()); + + /** + * The current compatibility version of the Header API. + * + * Increment this number when an incompatible change is made to the header (like the search form API). + */ + private static final int compatibilityHeaderVersion = 1; + + @Override + public final boolean isCompatible() { + return compatibilityHeaderVersion == getSupportedHeaderVersion(); + } + + /** + * @return the supported header version + */ + public abstract int getSupportedHeaderVersion(); + + @Initializer(after = InitMilestone.JOB_LOADED, before = InitMilestone.JOB_CONFIG_ADAPTED) + @SuppressWarnings("unused") + public static void incompatibleHeaders() { + ExtensionList.lookup(PartialHeader.class).stream().filter(h -> !h.isCompatible()).forEach(header -> { + LOGGER.warning(String.format("%s:%s not compatible with %s", header.getClass().getName(), header.getSupportedHeaderVersion(), compatibilityHeaderVersion)); + new AdministrativeError(header.getClass().getName(), "Incompatible Header", String.format("The plugin %s is attempting to replace the Jenkins header but is not compatible with this version of Jenkins. The plugin should be updated or removed.", Jenkins.get().getPluginManager().whichPlugin(header.getClass())), null); + }); + } +} \ No newline at end of file diff --git a/core/src/main/resources/jenkins/views/JenkinsHeader/headerContent.jelly b/core/src/main/resources/jenkins/views/JenkinsHeader/headerContent.jelly new file mode 100644 index 000000000000..6018c3b7f5b4 --- /dev/null +++ b/core/src/main/resources/jenkins/views/JenkinsHeader/headerContent.jelly @@ -0,0 +1,80 @@ + + + + diff --git a/core/src/main/resources/lib/layout/pageHeader.jelly b/core/src/main/resources/lib/layout/pageHeader.jelly index 63d137450049..d554a5bcfdd4 100644 --- a/core/src/main/resources/lib/layout/pageHeader.jelly +++ b/core/src/main/resources/lib/layout/pageHeader.jelly @@ -25,82 +25,6 @@ Text for the logout link - - + + From cfe77b40003d7f328b7c6e3af939612cfcea20ab Mon Sep 17 00:00:00 2001 From: James Nord Date: Fri, 26 Nov 2021 12:18:55 +0000 Subject: [PATCH 08/10] [JENKINS-67225] replace "default value" with "Set by Default" (#5955) --- .../hudson/model/BooleanParameterDefinition/config.jelly | 2 +- .../model/BooleanParameterDefinition/config_da.properties | 2 +- .../model/BooleanParameterDefinition/config_de.properties | 2 +- .../model/BooleanParameterDefinition/config_es.properties | 2 +- .../model/BooleanParameterDefinition/config_fr.properties | 2 +- .../model/BooleanParameterDefinition/config_it.properties | 4 ++-- .../model/BooleanParameterDefinition/config_ja.properties | 2 +- .../model/BooleanParameterDefinition/config_pl.properties | 2 +- .../model/BooleanParameterDefinition/config_pt_BR.properties | 2 +- .../model/BooleanParameterDefinition/config_ru.properties | 2 +- .../model/BooleanParameterDefinition/config_sr.properties | 2 +- .../model/BooleanParameterDefinition/config_zh_TW.properties | 2 +- 12 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config.jelly b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config.jelly index 045473819820..326951d9b0d2 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config.jelly +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config.jelly @@ -30,7 +30,7 @@ THE SOFTWARE. - + diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_da.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_da.properties index 1fa04ed0bbe5..67a5ea420c9d 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_da.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_da.properties @@ -21,5 +21,5 @@ # THE SOFTWARE. Name=Navn -Default\ Value=Standardv\u00e6rdi +Set\ by\ Default=Standardv\u00e6rdi Description=Beskrivelse diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_de.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_de.properties index ac7c983a0816..dbcdf8091bf3 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_de.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_de.properties @@ -1,3 +1,3 @@ Name=Name -Default\ Value=Vorgabewert +Set\ by\ Default=Vorgabewert Description=Beschreibung diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_es.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_es.properties index 0effa47580d5..866f50415953 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_es.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_es.properties @@ -21,5 +21,5 @@ # THE SOFTWARE. Name=Nombre -Default\ Value=Valor por defecto +Set\ by\ Default=Valor por defecto Description=Descripción diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_fr.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_fr.properties index 74d03dea8399..6b0bb760a0ba 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_fr.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_fr.properties @@ -21,5 +21,5 @@ # THE SOFTWARE. Name=Nom -Default\ Value=Valeur par d\u00E9faut +Set\ by\ Default=Valeur par d\u00E9faut Description=Description diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_it.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_it.properties index 26e1c75e5100..1b835fb39b2e 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_it.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_it.properties @@ -1,7 +1,7 @@ # The MIT License # # Italian localization plugin for Jenkins -# Copyright © 2020 Alessandro Menti +# Copyright © 2020 Alessandro Menti # # 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,6 +21,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -Default\ Value=Valore predefinito +Set\ by\ Default=Valore predefinito Description=Descrizione Name=Nome diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_ja.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_ja.properties index b07775bf194b..82a093e6013c 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_ja.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_ja.properties @@ -21,5 +21,5 @@ # THE SOFTWARE. Name=\u540D\u524D -Default\ Value=\u30C7\u30D5\u30A9\u30EB\u30C8\u5024 +Set\ by\ Default=\u30C7\u30D5\u30A9\u30EB\u30C8\u5024 Description=\u8AAC\u660E diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_pl.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_pl.properties index f825b49123fa..39916fd8a9b8 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_pl.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_pl.properties @@ -20,5 +20,5 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. Name=Nazwa -Default\ Value=Domy\u015Blna warto\u015B\u0107 +Set\ by\ Default=Domy\u015Blna warto\u015B\u0107 Description=Opis diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_pt_BR.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_pt_BR.properties index b98a4b482122..eb2839cc7d91 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_pt_BR.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_pt_BR.properties @@ -21,5 +21,5 @@ # THE SOFTWARE. Name=Nome -Default\ Value=Valor padr\u00e3o +Set\ by\ Default=Valor padr\u00e3o Description=Descri\u00e7\u00e3o diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_ru.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_ru.properties index 069c2794cb07..e2b5bba7640e 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_ru.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_ru.properties @@ -20,6 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -Default\ Value=\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E-\u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E +Set\ by\ Default=\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u043F\u043E-\u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E Description=\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 Name=\u0418\u043C\u044F diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_sr.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_sr.properties index 93b803761e8a..00c613919dae 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_sr.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_sr.properties @@ -1,5 +1,5 @@ # This file is under the MIT License by authors Name=\u0418\u043C\u0435 -Default\ Value=\u0421\u0442\u0430\u043D\u0434\u0430\u0440\u0434\u043D\u0430 \u0432\u0440\u0435\u0434\u043D\u043E\u0441\u0442 +Set\ by\ Default=\u0421\u0442\u0430\u043D\u0434\u0430\u0440\u0434\u043D\u0430 \u0432\u0440\u0435\u0434\u043D\u043E\u0441\u0442 Description=\u041E\u043F\u0438\u0441 diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_TW.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_TW.properties index a64f68980c41..f0944741073c 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_TW.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_TW.properties @@ -21,5 +21,5 @@ # THE SOFTWARE. Name=\u540d\u7a31 -Default\ Value=\u9810\u8a2d\u503c +Set\ by\ Default=\u9810\u8a2d\u503c Description=\u8aaa\u660e From 62cdc38dd01603a343a2592907881ef798af0212 Mon Sep 17 00:00:00 2001 From: Daniel Beck <1831569+daniel-beck@users.noreply.github.com> Date: Fri, 26 Nov 2021 13:19:02 +0100 Subject: [PATCH 09/10] [JENKINS-67226] Deduplicate RPCRequest oids and proxy objects (#5957) Co-authored-by: Daniel Beck --- .../impl/SlaveToMasterFileCallableUsage.java | 9 ++++- .../SlaveToMasterFileCallableUsageTest.java | 37 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsageTest.java diff --git a/core/src/main/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage.java b/core/src/main/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage.java index dd628b767773..3147946f4c8c 100644 --- a/core/src/main/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage.java +++ b/core/src/main/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage.java @@ -30,6 +30,7 @@ import java.util.Collections; import java.util.Set; import java.util.TreeSet; +import java.util.regex.Matcher; import jenkins.SlaveToMasterFileCallable; import jenkins.security.s2m.DefaultFilePathFilter; import jenkins.telemetry.Telemetry; @@ -69,7 +70,13 @@ public synchronized JSONObject createContent() { } public synchronized void recordTrace(Throwable trace) { - traces.add(Functions.printThrowable(trace).replaceAll("@[a-f0-9]+", "@…")); + traces.add(generalize(Functions.printThrowable(trace))); } + protected static String generalize(String trace) { + return trace + .replaceAll("@[a-f0-9]+", "@…") + .replaceAll("]\\([0-9]+\\) created at", "](…) created at") + .replaceAll("com[.]sun[.]proxy[.][$]Proxy[0-9]+[.]", Matcher.quoteReplacement("com.sun.proxy.$Proxy….")); + } } diff --git a/core/src/test/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsageTest.java b/core/src/test/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsageTest.java new file mode 100644 index 000000000000..ef1bdcc464df --- /dev/null +++ b/core/src/test/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsageTest.java @@ -0,0 +1,37 @@ +package jenkins.telemetry.impl; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class SlaveToMasterFileCallableUsageTest { + @Test + public void ignoreObjectIdentity() { + assertGeneralization("Command UserRequest:hudson.FilePath$CopyTo@… created at", "Command UserRequest:hudson.FilePath$CopyTo@abcdef12 created at"); + + assertUnmodified("FilePath$CopyTo-abcdef12 created at"); + assertUnmodified("abcdef12 created at"); + assertUnmodified("\tat hudson.FilePath.readToString(FilePath.java:2289)"); + } + + @Test + public void ignoreRPCRequestOid() { + assertGeneralization("Command UserRequest:UserRPCRequest:hudson.maven.MavenBuildProxy2.end[](…) created at", "Command UserRequest:UserRPCRequest:hudson.maven.MavenBuildProxy2.end[](16) created at"); + assertGeneralization("Command UserRequest:UserRPCRequest:hudson.maven.MavenBuildProxy2.end[org.acme.FakeType](…) created at", "Command UserRequest:UserRPCRequest:hudson.maven.MavenBuildProxy2.end[org.acme.FakeType](16) created at"); + } + + @Test + public void ignoreProxyIndex() { + assertGeneralization("at com.sun.proxy.$Proxy….end(Unknown Source)", "at com.sun.proxy.$Proxy6.end(Unknown Source)"); + assertGeneralization("at com.sun.proxy.$Proxy….end(Unknown Source)", "at com.sun.proxy.$Proxy66.end(Unknown Source)"); + assertGeneralization("at com.sun.proxy.$Proxy….begin(Unknown Source)", "at com.sun.proxy.$Proxy66.begin(Unknown Source)"); + } + + private static void assertGeneralization(String generalized, String actual) { + assertEquals(generalized, SlaveToMasterFileCallableUsage.generalize(actual)); + } + + private static void assertUnmodified(String actual) { + assertEquals(actual, SlaveToMasterFileCallableUsage.generalize(actual)); + } +} From d2fa87606246e1f1dc1450d7199778343945197e Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Fri, 26 Nov 2021 05:18:15 -0800 Subject: [PATCH 10/10] Enable `DefaultComesLast` Checkstyle check (#5963) --- .../main/java/hudson/util/LineEndingConversion.java | 10 +++++----- pom.xml | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/hudson/util/LineEndingConversion.java b/core/src/main/java/hudson/util/LineEndingConversion.java index c4df9b9b84ac..24e296fd36ad 100644 --- a/core/src/main/java/hudson/util/LineEndingConversion.java +++ b/core/src/main/java/hudson/util/LineEndingConversion.java @@ -50,15 +50,15 @@ public static String convertEOL(String input, EOLType type) { // Convert line endings to Windows CR/LF input = input.replace("\n", "\r\n"); break; - default: - case LF: - case Unix: - // Conversion already completed - return input; case LFCR: // Convert line endings to LF/CR input = input.replace("\n", "\n\r"); break; + case LF: + case Unix: + default: + // Conversion already completed + return input; } return input; } diff --git a/pom.xml b/pom.xml index 231f7a20bad3..f34c810c6af4 100644 --- a/pom.xml +++ b/pom.xml @@ -446,6 +446,7 @@ THE SOFTWARE. --> +