diff --git a/core/src/main/java/hudson/DependencyRunner.java b/core/src/main/java/hudson/DependencyRunner.java index acf035676910..03efea55e403 100644 --- a/core/src/main/java/hudson/DependencyRunner.java +++ b/core/src/main/java/hudson/DependencyRunner.java @@ -59,7 +59,7 @@ public void run() { Set topLevelProjects = new HashSet(); // Get all top-level projects LOGGER.fine("assembling top level projects"); - for (AbstractProject p : Jenkins.getInstance().getAllItems(AbstractProject.class)) + for (AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class)) if (p.getUpstreamProjects().size() == 0) { LOGGER.fine("adding top level project " + p.getName()); topLevelProjects.add(p); diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index f372a5413c06..661c7ec77fae 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -778,7 +778,7 @@ public List getTiedJobs() { } public RunList getBuilds() { - return new RunList(Jenkins.getInstance().getAllItems(Job.class)).node(getNode()); + return RunList.fromJobs(Jenkins.getInstance().allItems(Job.class)).node(getNode()); } /** diff --git a/core/src/main/java/hudson/model/DependencyGraph.java b/core/src/main/java/hudson/model/DependencyGraph.java index 41327d009d33..237eba1b40f5 100644 --- a/core/src/main/java/hudson/model/DependencyGraph.java +++ b/core/src/main/java/hudson/model/DependencyGraph.java @@ -91,7 +91,7 @@ public void build() { SecurityContext saveCtx = ACL.impersonate(ACL.SYSTEM); try { this.computationalData = new HashMap, Object>(); - for( AbstractProject p : getAllProjects() ) + for( AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class) ) p.buildDependencyGraph(this); forward = finalize(forward); @@ -147,10 +147,6 @@ public int compare(AbstractProject o1, AbstractProject o2) { topologicallySorted = Collections.unmodifiableList(topologicallySorted); } - Collection getAllProjects() { - return Jenkins.getInstance().getAllItems(AbstractProject.class); - } - /** * Special constructor for creating an empty graph */ diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java index 0b457293ab29..7ab358538645 100644 --- a/core/src/main/java/hudson/model/Items.java +++ b/core/src/main/java/hudson/model/Items.java @@ -30,29 +30,30 @@ import hudson.model.listeners.ItemListener; import hudson.remoting.Callable; import hudson.security.ACL; +import hudson.security.ACLContext; import hudson.security.AccessControlled; import hudson.triggers.Trigger; import hudson.util.DescriptorList; import hudson.util.EditDistance; import hudson.util.XStream2; -import jenkins.model.Jenkins; -import org.acegisecurity.Authentication; -import org.apache.commons.lang.StringUtils; - import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import java.util.Stack; import java.util.StringTokenizer; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; - import jenkins.model.DirectlyModifiableTopLevelItemGroup; +import jenkins.model.Jenkins; +import org.acegisecurity.Authentication; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; /** * Convenience methods related to {@link Item}. @@ -79,6 +80,44 @@ public class Items { return false; } }; + /** + * A comparator of {@link Item} instances that uses a case-insensitive comparison of {@link Item#getName()}. + * If you are replacing {@link #getAllItems(ItemGroup, Class)} with {@link #allItems(ItemGroup, Class)} and + * need to restore the sort order of a further filtered result, you probably want {@link #BY_FULL_NAME}. + * + * @since FIXME + */ + public static final Comparator BY_NAME = new Comparator() { + @Override public int compare(Item i1, Item i2) { + return name(i1).compareToIgnoreCase(name(i2)); + } + + String name(Item i) { + String n = i.getName(); + if (i instanceof ItemGroup) { + n += '/'; + } + return n; + } + }; + /** + * A comparator of {@link Item} instances that uses a case-insensitive comparison of {@link Item#getFullName()}. + * + * @since FIXME + */ + public static final Comparator BY_FULL_NAME = new Comparator() { + @Override public int compare(Item i1, Item i2) { + return name(i1).compareToIgnoreCase(name(i2)); + } + + String name(Item i) { + String n = i.getFullName(); + if (i instanceof ItemGroup) { + n += '/'; + } + return n; + } + }; /** * Runs a block while making {@link #currentlyUpdatingByXml} be temporarily true. @@ -350,7 +389,12 @@ public static XmlFile getConfigFile(Item item) { /** * Gets all the {@link Item}s recursively in the {@link ItemGroup} tree - * and filter them by the given type. + * and filter them by the given type. The returned list will represent a snapshot view of the items present at some + * time during the call. If items are moved during the call, depending on the move, it may be possible for some + * items to escape the snapshot entirely. + *

+ * If you do not need to iterate all items, or if the order of the items is not required, consider using + * {@link #allItems(ItemGroup, Class)} instead. * * @since 1.512 */ @@ -361,18 +405,8 @@ public static List getAllItems(final ItemGroup root, Class void getAllItems(final ItemGroup root, Class type, List r) { List items = new ArrayList(((ItemGroup) root).getItems()); - Collections.sort(items, new Comparator() { - @Override public int compare(Item i1, Item i2) { - return name(i1).compareToIgnoreCase(name(i2)); - } - String name(Item i) { - String n = i.getName(); - if (i instanceof ItemGroup) { - n += '/'; - } - return n; - } - }); + // because we add items depth first, we can use the quicker BY_NAME comparison + Collections.sort(items, BY_NAME); for (Item i : items) { if (type.isInstance(i)) { if (i.hasPermission(Item.READ)) { @@ -385,6 +419,41 @@ String name(Item i) { } } + /** + * Gets a read-only view of all the {@link Item}s recursively in the {@link ItemGroup} tree visible to + * {@link Jenkins#getAuthentication()} without concern for the order in which items are returned. Each iteration + * of the view will be "live" reflecting the items available between the time the iteration was started and the + * time the iteration was completed, however if items are moved during an iteration - depending on the move - it + * may be possible for such items to escape the entire iteration. + * + * @param root the root. + * @param type the type. + * @param the type. + * @return An {@link Iterable} for all items. + * @since FIXME + */ + public static Iterable allItems(ItemGroup root, Class type) { + return allItems(Jenkins.getAuthentication(), root, type); + } + + + /** + * Gets a read-only view all the {@link Item}s recursively in the {@link ItemGroup} tree visible to the supplied + * authentication without concern for the order in which items are returned. Each iteration + * of the view will be "live" reflecting the items available between the time the iteration was started and the + * time the iteration was completed, however if items are moved during an iteration - depending on the move - it + * may be possible for such items to escape the entire iteration. + * + * @param root the root. + * @param type the type. + * @param the type. + * @return An {@link Iterable} for all items. + * @since FIXME + */ + public static Iterable allItems(Authentication authentication, ItemGroup root, Class type) { + return new AllItemsIterable<>(root, authentication, type); + } + /** * Finds an item whose name (when referenced from the specified context) is closest to the given name. * @param the type of item being considered @@ -395,10 +464,9 @@ String name(Item i) { * @since 1.538 */ public static @CheckForNull T findNearest(Class type, String name, ItemGroup context) { - List projects = Jenkins.getInstance().getAllItems(type); - String[] names = new String[projects.size()]; - for (int i = 0; i < projects.size(); i++) { - names[i] = projects.get(i).getRelativeNameFrom(context); + List names = new ArrayList<>(); + for (T item: Jenkins.getInstance().allItems(type)) { + names.add(item.getRelativeNameFrom(context)); } String nearest = EditDistance.findNearest(name, names); return Jenkins.getInstance().getItem(nearest, context, type); @@ -440,6 +508,121 @@ public static I move(I item, DirectlyMod return newItem; } + private static class AllItemsIterable implements Iterable { + + /** + * The authentication we are iterating as. + */ + private final Authentication authentication; + /** + * The root we are iterating from. + */ + private final ItemGroup root; + /** + * The type of item we want to return. + */ + private final Class type; + + private AllItemsIterable(ItemGroup root, Authentication authentication, Class type) { + this.root = root; + this.authentication = authentication; + this.type = type; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator iterator() { + return new AllItemsIterator(); + } + + private class AllItemsIterator implements Iterator { + + /** + * The stack of {@link ItemGroup}s that we have left to descend into. + */ + private final Stack stack = new Stack<>(); + /** + * The iterator of the current {@link ItemGroup} we are iterating. + */ + private Iterator delegate = null; + /** + * The next item. + */ + private T next = null; + + private AllItemsIterator() { + // put on the stack so that hasNext() is the only place that has to worry about authentication + // alternative would be to impersonate and populate delegate. + stack.push(root); + } + + /** + * {@inheritDoc} + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasNext() { + if (next != null) { + return true; + } + while (true) { + if (delegate == null || !delegate.hasNext()) { + if (stack.isEmpty()) { + return false; + } + ItemGroup group = stack.pop(); + // group.getItems() is responsible for performing the permission check so we will not repeat it + if (Jenkins.getAuthentication() == authentication) { + delegate = group.getItems().iterator(); + } else { + // slower path because the caller has switched authentication + // we need to keep the original authentication so that allItems() can be used + // like getAllItems() without the cost of building the entire list up front + try (ACLContext ctx = ACL.as(authentication)) { + delegate = group.getItems().iterator(); + } + } + } + while (delegate.hasNext()) { + Item item = delegate.next(); + if (item instanceof ItemGroup) { + stack.push((ItemGroup) item); + } + if (type.isInstance(item)) { + next = type.cast(item); + return true; + } + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + try { + return next; + } finally { + next = null; + } + } + + } + } + /** * Used to load/save job configuration. * diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java index 74a9fa690161..6c3692585099 100644 --- a/core/src/main/java/hudson/model/Label.java +++ b/core/src/main/java/hudson/model/Label.java @@ -362,10 +362,11 @@ private String toString(Collection model) { @Exported public List getTiedJobs() { List r = new ArrayList(); - for (AbstractProject p : Jenkins.getInstance().getAllItems(AbstractProject.class)) { + for (AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class)) { if(p instanceof TopLevelItem && this.equals(p.getAssignedLabel())) r.add(p); } + Collections.sort(r, Items.BY_FULL_NAME); return r; } diff --git a/core/src/main/java/hudson/model/ListView.java b/core/src/main/java/hudson/model/ListView.java index 68ded29c6f5f..32e3779e3042 100644 --- a/core/src/main/java/hudson/model/ListView.java +++ b/core/src/main/java/hudson/model/ListView.java @@ -480,7 +480,7 @@ private void locationChanged(Item item, String oldFullName, String newFullName) renameViewItem(oldFullName, newFullName, jenkins, (ListView) view); } } - for (Item g : jenkins.getAllItems()) { + for (Item g : jenkins.allItems()) { if (g instanceof ViewGroup) { ViewGroup vg = (ViewGroup) g; for (View v : vg.getViews()) { @@ -524,7 +524,7 @@ private void deleted(Item item) { deleteViewItem(item, jenkins, (ListView) view); } } - for (Item g : jenkins.getAllItems()) { + for (Item g : jenkins.allItems()) { if (g instanceof ViewGroup) { ViewGroup vg = (ViewGroup) g; for (View v : vg.getViews()) { diff --git a/core/src/main/java/hudson/model/UsageStatistics.java b/core/src/main/java/hudson/model/UsageStatistics.java index 4f46b292d4d3..2118d67d7033 100644 --- a/core/src/main/java/hudson/model/UsageStatistics.java +++ b/core/src/main/java/hudson/model/UsageStatistics.java @@ -158,14 +158,22 @@ public String getStatData() throws IOException { o.put("plugins",plugins); JSONObject jobs = new JSONObject(); - List items = j.getAllItems(TopLevelItem.class); - for (TopLevelItemDescriptor d : Items.all()) { - int cnt=0; - for (TopLevelItem item : items) { - if(item.getDescriptor()==d) - cnt++; + // capture the descriptors as these should be small compared with the number of items + // so we will walk all items only once and we can short-cut the search of descriptors + TopLevelItemDescriptor[] descriptors = Items.all().toArray(new TopLevelItemDescriptor[0]); + int counts[] = new int[descriptors.length]; + for (TopLevelItem item: j.allItems(TopLevelItem.class)) { + TopLevelItemDescriptor d = item.getDescriptor(); + for (int i = 0; i < descriptors.length; i++) { + if (d == descriptors[i]) { + counts[i]++; + // no point checking any more, we found the match + break; + } } - jobs.put(d.getJsonSafeClassName(),cnt); + } + for (int i = 0; i < descriptors.length; i++) { + jobs.put(descriptors[i].getJsonSafeClassName(), counts[i]); } o.put("jobs",jobs); diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index 6ff891f42d18..82e0a7607e70 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -24,11 +24,16 @@ */ package hudson.model; -import jenkins.security.UserDetailsCache; -import jenkins.util.SystemProperties; import com.google.common.base.Predicate; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; -import hudson.*; +import hudson.BulkChange; +import hudson.CopyOnWrite; +import hudson.Extension; +import hudson.ExtensionList; +import hudson.ExtensionPoint; +import hudson.FeedAdapter; +import hudson.Util; +import hudson.XmlFile; import hudson.model.Descriptor.FormException; import hudson.model.listeners.SaveableListener; import hudson.security.ACL; @@ -40,36 +45,9 @@ import hudson.util.FormValidation; import hudson.util.RunList; import hudson.util.XStream2; -import jenkins.model.IdStrategy; -import jenkins.model.Jenkins; -import jenkins.model.ModelObjectWithContextMenu; -import jenkins.security.ImpersonatingUserDetailsService; -import jenkins.security.LastGrantedAuthoritiesProperty; -import net.sf.json.JSONObject; - -import org.acegisecurity.Authentication; -import org.acegisecurity.GrantedAuthority; -import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; -import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; -import org.acegisecurity.userdetails.UserDetails; -import org.acegisecurity.userdetails.UsernameNotFoundException; -import org.jenkinsci.Symbol; -import org.springframework.dao.DataAccessException; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; -import org.kohsuke.stapler.export.Exported; -import org.kohsuke.stapler.export.ExportedBean; -import org.apache.commons.io.filefilter.DirectoryFileFilter; -import org.kohsuke.stapler.interceptor.RequirePOST; - -import javax.annotation.concurrent.GuardedBy; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletResponse; import java.io.File; -import java.io.IOException; import java.io.FileFilter; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -91,7 +69,34 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; +import jenkins.model.IdStrategy; +import jenkins.model.Jenkins; +import jenkins.model.ModelObjectWithContextMenu; +import jenkins.security.ImpersonatingUserDetailsService; +import jenkins.security.LastGrantedAuthoritiesProperty; +import jenkins.security.UserDetailsCache; +import jenkins.util.SystemProperties; +import net.sf.json.JSONObject; +import org.acegisecurity.Authentication; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.userdetails.UsernameNotFoundException; +import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.apache.commons.lang.StringUtils; +import org.jenkinsci.Symbol; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.export.Exported; +import org.kohsuke.stapler.export.ExportedBean; +import org.kohsuke.stapler.interceptor.RequirePOST; +import org.springframework.dao.DataAccessException; /** * Represents a user. @@ -647,9 +652,10 @@ private boolean relatedTo(@Nonnull AbstractBuild b) { * Gets the list of {@link Build}s that include changes by this user, * by the timestamp order. */ + @SuppressWarnings("unchecked") @WithBridgeMethods(List.class) public @Nonnull RunList getBuilds() { - return new RunList>(Jenkins.getInstance().getAllItems(Job.class)).filter(new Predicate>() { + return RunList.fromJobs(Jenkins.getInstance().allItems(Job.class)).filter(new Predicate>() { @Override public boolean apply(Run r) { return r instanceof AbstractBuild && relatedTo((AbstractBuild) r); } @@ -662,7 +668,7 @@ private boolean relatedTo(@Nonnull AbstractBuild b) { */ public @Nonnull Set> getProjects() { Set> r = new HashSet>(); - for (AbstractProject p : Jenkins.getInstance().getAllItems(AbstractProject.class)) + for (AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class)) if(p.hasParticipant(this)) r.add(p); return r; @@ -833,7 +839,7 @@ public void doRssFailed(StaplerRequest req, StaplerResponse rsp) throws IOExcept public void doRssLatest(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { final List lastBuilds = new ArrayList(); - for (AbstractProject p : Jenkins.getInstance().getAllItems(AbstractProject.class)) { + for (AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class)) { for (AbstractBuild b = p.getLastBuild(); b != null; b = b.getPreviousBuild()) { if (relatedTo(b)) { lastBuilds.add(b); @@ -841,6 +847,14 @@ public void doRssLatest(StaplerRequest req, StaplerResponse rsp) throws IOExcept } } } + // historically these have been reported sorted by project name, we switched to the lazy iteration + // so we only have to sort the sublist of runs rather than the full list of irrelevant projects + Collections.sort(lastBuilds, new Comparator() { + @Override + public int compare(Run o1, Run o2) { + return Items.BY_FULL_NAME.compare(o1.getParent(), o2.getParent()); + } + }); rss(req, rsp, " latest build", RunList.fromRuns(lastBuilds), Run.FEED_ADAPTER_LATEST); } diff --git a/core/src/main/java/hudson/model/WorkspaceCleanupThread.java b/core/src/main/java/hudson/model/WorkspaceCleanupThread.java index e15228f3d756..913d86e3eeaa 100644 --- a/core/src/main/java/hudson/model/WorkspaceCleanupThread.java +++ b/core/src/main/java/hudson/model/WorkspaceCleanupThread.java @@ -68,7 +68,7 @@ public static void invoke() { Jenkins j = Jenkins.getInstance(); nodes.add(j); nodes.addAll(j.getNodes()); - for (TopLevelItem item : j.getAllItems(TopLevelItem.class)) { + for (TopLevelItem item : j.allItems(TopLevelItem.class)) { if (item instanceof ModifiableTopLevelItemGroup) { // no such thing as TopLevelItemGroup, and ItemGroup offers no access to its type parameter continue; // children will typically have their own workspaces as subdirectories; probably no real workspace of its own } diff --git a/core/src/main/java/hudson/model/listeners/ItemListener.java b/core/src/main/java/hudson/model/listeners/ItemListener.java index 7ff530c43816..e5f88320f747 100644 --- a/core/src/main/java/hudson/model/listeners/ItemListener.java +++ b/core/src/main/java/hudson/model/listeners/ItemListener.java @@ -240,11 +240,7 @@ public static void fireLocationChange(final Item rootItem, final String oldFullN } }); if (rootItem instanceof ItemGroup) { - for (final Item child : ACL.impersonate(ACL.SYSTEM, new NotReallyRoleSensitiveCallable,RuntimeException>() { - @Override public List call() { - return Items.getAllItems((ItemGroup) rootItem, Item.class); - } - })) { + for (final Item child : Items.allItems(ACL.SYSTEM, (ItemGroup)rootItem, Item.class)) { final String childNew = child.getFullName(); assert childNew.startsWith(newFullName); assert childNew.charAt(newFullName.length()) == '/'; diff --git a/core/src/main/java/hudson/scm/AutoBrowserHolder.java b/core/src/main/java/hudson/scm/AutoBrowserHolder.java index 10e03cb0f64a..001d31487180 100644 --- a/core/src/main/java/hudson/scm/AutoBrowserHolder.java +++ b/core/src/main/java/hudson/scm/AutoBrowserHolder.java @@ -76,7 +76,7 @@ public RepositoryBrowser get() { * null if no applicable configuration was found. */ private RepositoryBrowser infer() { - for( AbstractProject p : Jenkins.getInstance().getAllItems(AbstractProject.class) ) { + for( AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class) ) { SCM scm = p.getScm(); if (scm!=null && scm.getClass()==owner.getClass() && scm.getBrowser()!=null && ((SCMDescriptor)scm.getDescriptor()).isBrowserReusable(scm,owner)) { diff --git a/core/src/main/java/hudson/search/CollectionSearchIndex.java b/core/src/main/java/hudson/search/CollectionSearchIndex.java index 26422553c4e4..bed7ca7ea8b3 100644 --- a/core/src/main/java/hudson/search/CollectionSearchIndex.java +++ b/core/src/main/java/hudson/search/CollectionSearchIndex.java @@ -24,8 +24,10 @@ package hudson.search; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; +import javax.annotation.Nonnull; /** * {@link SearchIndex} built on a {@link Map}. @@ -44,6 +46,12 @@ public abstract class CollectionSearchIndex i */ protected abstract Collection all(); + @Nonnull + protected Iterable allAsIterable() { + Collection all = all(); + return all == null ? Collections.emptySet() : all; + } + public void find(String token, List result) { SearchItem p = get(token); if(p!=null) @@ -51,13 +59,11 @@ public void find(String token, List result) { } public void suggest(String token, List result) { - Collection items = all(); boolean isCaseSensitive = UserSearchProperty.isCaseInsensitive(); if(isCaseSensitive){ token = token.toLowerCase(); } - if(items==null) return; - for (SMT o : items) { + for (SMT o : allAsIterable()) { String name = getName(o); if(isCaseSensitive) name=name.toLowerCase(); diff --git a/core/src/main/java/hudson/tasks/ArtifactArchiver.java b/core/src/main/java/hudson/tasks/ArtifactArchiver.java index ca1f154f1563..b7d58e318321 100644 --- a/core/src/main/java/hudson/tasks/ArtifactArchiver.java +++ b/core/src/main/java/hudson/tasks/ArtifactArchiver.java @@ -348,7 +348,7 @@ public boolean isApplicable(Class jobType) { @Extension public static final class Migrator extends ItemListener { @SuppressWarnings("deprecation") @Override public void onLoaded() { - for (AbstractProject p : Jenkins.getInstance().getAllItems(AbstractProject.class)) { + for (AbstractProject p : Jenkins.getInstance().allItems(AbstractProject.class)) { try { ArtifactArchiver aa = p.getPublishersList().get(ArtifactArchiver.class); if (aa != null && aa.latestOnly != null) { diff --git a/core/src/main/java/hudson/tasks/BuildTrigger.java b/core/src/main/java/hudson/tasks/BuildTrigger.java index 21dd6b3d0c9b..d2b0a91f6b0d 100644 --- a/core/src/main/java/hudson/tasks/BuildTrigger.java +++ b/core/src/main/java/hudson/tasks/BuildTrigger.java @@ -420,7 +420,7 @@ public void onLocationChanged(final Item item, final String oldFullName, final S private void locationChanged(Item item, String oldFullName, String newFullName) { // update BuildTrigger of other projects that point to this object. // can't we generalize this? - for( Project p : Jenkins.getInstance().getAllItems(Project.class) ) { + for( Project p : Jenkins.getInstance().allItems(Project.class) ) { BuildTrigger t = p.getPublishersList().get(BuildTrigger.class); if(t!=null) { String cp2 = Items.computeRelativeNamesAfterRenaming(oldFullName, newFullName, t.childProjects, p.getParent()); diff --git a/core/src/main/java/hudson/triggers/SCMTrigger.java b/core/src/main/java/hudson/triggers/SCMTrigger.java index 577df2acf50b..77d98f6490e3 100644 --- a/core/src/main/java/hudson/triggers/SCMTrigger.java +++ b/core/src/main/java/hudson/triggers/SCMTrigger.java @@ -310,12 +310,24 @@ public void setPollingThreadCount(int n) { @Restricted(NoExternalUse.class) public boolean isPollingThreadCountOptionVisible() { + if (getPollingThreadCount() != 0) { + // this is a user who already configured the option + return true; + } // unless you have a fair number of projects, this option is likely pointless. // so let's hide this option for new users to avoid confusing them // unless it was already changed - // TODO switch to check for SCMTriggerItem - return Jenkins.getInstance().getAllItems(AbstractProject.class).size() > 10 - || getPollingThreadCount() != 0; + int count = 0; + // we are faster walking some items with a lazy iterator than building a list of all items just to query + // the size. This also lets us check against SCMTriggerItem rather than AbstractProject + for (Item item: Jenkins.getInstance().allItems(Item.class)) { + if (item instanceof SCMTriggerItem) { + if (++count > 10) { + return true; + } + } + } + return false; } /** diff --git a/core/src/main/java/hudson/triggers/Trigger.java b/core/src/main/java/hudson/triggers/Trigger.java index 1da137237c7c..e578de88983e 100644 --- a/core/src/main/java/hudson/triggers/Trigger.java +++ b/core/src/main/java/hudson/triggers/Trigger.java @@ -266,7 +266,7 @@ public void run(AbstractProject p) { } // Process all triggers, except SCMTriggers when synchronousPolling is set - for (ParameterizedJobMixIn.ParameterizedJob p : inst.getAllItems(ParameterizedJobMixIn.ParameterizedJob.class)) { + for (ParameterizedJobMixIn.ParameterizedJob p : inst.allItems(ParameterizedJobMixIn.ParameterizedJob.class)) { for (Trigger t : p.getTriggers().values()) { if (!(t instanceof SCMTrigger && scmd.synchronousPolling)) { if (t !=null && t.spec != null && t.tabs != null) { diff --git a/core/src/main/java/hudson/util/RunList.java b/core/src/main/java/hudson/util/RunList.java index f77232afcc68..8b9bd7683d45 100644 --- a/core/src/main/java/hudson/util/RunList.java +++ b/core/src/main/java/hudson/util/RunList.java @@ -76,7 +76,23 @@ public RunList(Collection jobs) { this.base = combine(runLists); } - private Iterable combine(Iterable> runLists) { + /** + * Createsa a {@link RunList} combining all the runs of the supplied jobs. + * + * @param jobs the supplied jobs. + * @param the base class of job. + * @param the base class of run. + * @return the run list. + * @since FIXME + */ + public static , R extends Run> RunList fromJobs(Iterable jobs) { + List> runLists = new ArrayList<>(); + for (Job j : jobs) + runLists.add(j.getBuilds()); + return new RunList<>(combine(runLists)); + } + + private static Iterable combine(Iterable> runLists) { return Iterables.mergeSorted(runLists, new Comparator() { public int compare(R o1, R o2) { long lhs = o1.getTimeInMillis(); diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 7c61deebdd87..45d0cfdbb52a 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -1753,6 +1753,16 @@ public List getAllItems(Class type) { return Items.getAllItems(this, type); } + /** + * Gets all the {@link Item}s unordered, lazily and recursively in the {@link ItemGroup} tree + * and filter them by the given type. + * + * @since FIXME + */ + public Iterable allItems(Class type) { + return Items.allItems(this, type); + } + /** * Gets all the items recursively. * @@ -1762,6 +1772,15 @@ public List getAllItems() { return getAllItems(Item.class); } + /** + * Gets all the items unordered, lazily and recursively. + * + * @since FIXME + */ + public Iterable allItems() { + return allItems(Item.class); + } + /** * Gets a list of simple top-level projects. * @deprecated This method will ignore Maven and matrix projects, as well as projects inside containers such as folders. @@ -1781,8 +1800,9 @@ public List getProjects() { */ public Collection getJobNames() { List names = new ArrayList(); - for (Job j : getAllItems(Job.class)) + for (Job j : allItems(Job.class)) names.add(j.getFullName()); + Collections.sort(names, String.CASE_INSENSITIVE_ORDER); return names; } @@ -2248,6 +2268,11 @@ public SearchIndexBuilder makeSearchIndex() { .add(new CollectionSearchIndex() { protected SearchItem get(String key) { return getItemByFullName(key, TopLevelItem.class); } protected Collection all() { return getAllItems(TopLevelItem.class); } + @Nonnull + @Override + protected Iterable allAsIterable() { + return allItems(TopLevelItem.class); + } }) .add(getPrimaryView().makeSearchIndex()) .add(new CollectionSearchIndex() {// for computers diff --git a/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java b/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java index c9f432e6d1e7..e9be86f3dd65 100644 --- a/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java +++ b/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java @@ -216,7 +216,7 @@ synchronized void invalidateCache() { private Map> calculateCache() { try (ACLContext _ = ACL.as(ACL.SYSTEM)) { final Map> result = new WeakHashMap<>(); - for (Job downstream : Jenkins.getInstance().getAllItems(Job.class)) { + for (Job downstream : Jenkins.getInstance().allItems(Job.class)) { ReverseBuildTrigger trigger = ParameterizedJobMixIn.getTrigger(downstream, ReverseBuildTrigger.class); if (trigger == null) { @@ -276,7 +276,7 @@ public static class ItemListenerImpl extends ItemListener { @Override public void onLocationChanged(Item item, final String oldFullName, final String newFullName) { try (ACLContext _ = ACL.as(ACL.SYSTEM)) { - for (Job p : Jenkins.getInstance().getAllItems(Job.class)) { + for (Job p : Jenkins.getInstance().allItems(Job.class)) { ReverseBuildTrigger t = ParameterizedJobMixIn.getTrigger(p, ReverseBuildTrigger.class); if (t != null) { String revised = diff --git a/test/src/test/java/hudson/model/ItemsTest.java b/test/src/test/java/hudson/model/ItemsTest.java index 59e168b4e68a..e3defc19a2e4 100644 --- a/test/src/test/java/hudson/model/ItemsTest.java +++ b/test/src/test/java/hudson/model/ItemsTest.java @@ -28,6 +28,8 @@ import java.util.Arrays; import org.junit.Test; + +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.*; import org.junit.Rule; import org.junit.rules.TemporaryFolder; @@ -61,6 +63,31 @@ public class ItemsTest { assertEquals(Arrays.asList(sub2a, sub2ap, sub2alpha, sub2b, sub2bp, sub2BRAVO, sub2c, sub2cp, sub2charlie), Items.getAllItems(sub2, Item.class)); } + @Issue("JENKINS-40252") + @Test + public void allItems() throws Exception { + MockFolder d = r.createFolder("d"); + MockFolder sub2 = d.createProject(MockFolder.class, "sub2"); + MockFolder sub2a = sub2.createProject(MockFolder.class, "a"); + MockFolder sub2c = sub2.createProject(MockFolder.class, "c"); + MockFolder sub2b = sub2.createProject(MockFolder.class, "b"); + MockFolder sub1 = d.createProject(MockFolder.class, "sub1"); + FreeStyleProject root = r.createFreeStyleProject("root"); + FreeStyleProject dp = d.createProject(FreeStyleProject.class, "p"); + FreeStyleProject sub1q = sub1.createProject(FreeStyleProject.class, "q"); + FreeStyleProject sub1p = sub1.createProject(FreeStyleProject.class, "p"); + FreeStyleProject sub2ap = sub2a.createProject(FreeStyleProject.class, "p"); + FreeStyleProject sub2bp = sub2b.createProject(FreeStyleProject.class, "p"); + FreeStyleProject sub2cp = sub2c.createProject(FreeStyleProject.class, "p"); + FreeStyleProject sub2alpha = sub2.createProject(FreeStyleProject.class, "alpha"); + FreeStyleProject sub2BRAVO = sub2.createProject(FreeStyleProject.class, "BRAVO"); + FreeStyleProject sub2charlie = sub2.createProject(FreeStyleProject.class, "charlie"); + assertThat(Items.allItems(d, FreeStyleProject.class), containsInAnyOrder(dp, sub1p, sub1q, sub2ap, sub2alpha, + sub2bp, sub2BRAVO, sub2cp, sub2charlie)); + assertThat(Items.allItems(sub2, Item.class), containsInAnyOrder((Item)sub2a, sub2ap, sub2alpha, sub2b, sub2bp, + sub2BRAVO, sub2c, sub2cp, sub2charlie)); + } + @Issue("JENKINS-24825") @Test public void moveItem() throws Exception { File tmp = tmpRule.getRoot();