Skip to content

Commit

Permalink
[FIXED JENKINS-40252] Add an Iterable<T> that returns all items unsorted
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenc committed Dec 14, 2016
1 parent 1868c84 commit cf0fea0
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 5 deletions.
155 changes: 150 additions & 5 deletions core/src/main/java/hudson/model/Items.java
Expand Up @@ -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}.
Expand Down Expand Up @@ -385,6 +386,35 @@ String name(Item i) {
}
}

/**
* Gets 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.
*
* @param root the root.
* @param type the type.
* @param <T> the type.
* @return An {@link Iterable} for all items.
* @since FIXME
*/
public static <T extends Item> Iterable<T> allItems(ItemGroup root, Class<T> type) {
return allItems(Jenkins.getAuthentication(), root, type);
}


/**
* Gets 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.
*
* @param root the root.
* @param type the type.
* @param <T> the type.
* @return An {@link Iterable} for all items.
* @since FIXME
*/
public static <T extends Item> Iterable<T> allItems(Authentication authentication, ItemGroup root, Class<T> 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 <T> the type of item being considered
Expand Down Expand Up @@ -440,6 +470,121 @@ public static <I extends AbstractItem & TopLevelItem> I move(I item, DirectlyMod
return newItem;
}

private static class AllItemsIterable<T extends Item> implements Iterable<T> {

/**
* 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<T> type;

private AllItemsIterable(ItemGroup root, Authentication authentication, Class<T> type) {
this.root = root;
this.authentication = authentication;
this.type = type;
}

/**
* {@inheritDoc}
*/
@Override
public Iterator<T> iterator() {
return new AllItemsIterator();
}

private class AllItemsIterator implements Iterator<T> {

/**
* The stack of {@link ItemGroup}s that we have left to descend into.
*/
private final Stack<ItemGroup> stack = new Stack<>();
/**
* The iterator of the current {@link ItemGroup} we
*/
private Iterator<Item> 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.
*
Expand Down
27 changes: 27 additions & 0 deletions test/src/test/java/hudson/model/ItemsTest.java
Expand Up @@ -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;
Expand Down Expand Up @@ -61,6 +63,31 @@ public class ItemsTest {
assertEquals(Arrays.<Item>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();
Expand Down

0 comments on commit cf0fea0

Please sign in to comment.