/******************************************************************************* * * Copyright (c) 2004-2010 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Kohsuke Kawaguchi, Martin Eigenbrodt. Seiji Sogabe, Alan Harder * * *******************************************************************************/ package hudson.model; import hudson.security.ACL; import hudson.security.NotSerilizableSecurityContext; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.Stack; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; /** * Maintains the build dependencies between {@link AbstractProject}s for * efficient dependency computation. * * <p> The "master" data of dependencies are owned/persisted/maintained by * individual {@link AbstractProject}s, but because of that, it's relatively * slow to compute backward edges. * * <p> This class builds the complete bi-directional dependency graph by * collecting information from all {@link AbstractProject}s. * * <p> Once built, {@link DependencyGraph} is immutable, and every time there's * a change (which is relatively rare), a new instance will be created. This * eliminates the need of synchronization. * * @see Hudson#getDependencyGraph() * @author Kohsuke Kawaguchi */ public final class DependencyGraph implements Comparator<AbstractProject> { private Map<AbstractProject, List<DependencyGroup>> forward = new HashMap<AbstractProject, List<DependencyGroup>>(); private Map<AbstractProject, List<DependencyGroup>> backward = new HashMap<AbstractProject, List<DependencyGroup>>(); private boolean built; /** * A unique set that holds the list of projects that have already computed * its dependency graph */ private Set<AbstractProject> alreadyComputedProjects = new HashSet<AbstractProject>(); /** * Builds the dependency graph. */ public DependencyGraph() { // Set full privileges while computing to avoid missing any projects the current user cannot see. // Use setContext (NOT getContext().setAuthentication()) so we don't affect concurrent threads for same HttpSession. SecurityContext saveCtx = SecurityContextHolder.getContext(); try { NotSerilizableSecurityContext system = new NotSerilizableSecurityContext(); system.setAuthentication(ACL.SYSTEM); SecurityContextHolder.setContext(system); for (AbstractProject p : Hudson.getInstance().getAllItems(AbstractProject.class)) { p.buildDependencyGraph(this); } forward = finalize(forward); backward = finalize(backward); built = true; alreadyComputedProjects.clear(); } finally { if (saveCtx.getAuthentication() == null) { SecurityContextHolder.clearContext(); } else { SecurityContextHolder.setContext(saveCtx); } } } /** * Special constructor for creating an empty graph */ private DependencyGraph(boolean dummy) { forward = backward = Collections.emptyMap(); built = true; } /** * Add this project to the set of projects that have already computed its * dependency graph * * @param project */ public void addToAlreadyComputedProjects(AbstractProject project) { alreadyComputedProjects.add(project); } /** * Check if the project has already computed its dependency graph * * @param project */ public boolean isAlreadyComputedProject(AbstractProject project) { return alreadyComputedProjects.contains(this); } /** * Gets all the immediate downstream projects (IOW forward edges) of the * given project. * * @return can be empty but never null. */ public List<AbstractProject> getDownstream(AbstractProject p) { return get(forward, p, false); } /** * Gets all the immediate upstream projects (IOW backward edges) of the * given project. * * @return can be empty but never null. */ public List<AbstractProject> getUpstream(AbstractProject p) { return get(backward, p, true); } private List<AbstractProject> get(Map<AbstractProject, List<DependencyGroup>> map, AbstractProject src, boolean up) { List<DependencyGroup> v = map.get(src); if (v == null) { return Collections.emptyList(); } List<AbstractProject> result = new ArrayList<AbstractProject>(v.size()); for (Dependency d : v) { result.add(up ? d.getUpstreamProject() : d.getDownstreamProject()); } return result; } /** * @since 1.341 */ public List<Dependency> getDownstreamDependencies(AbstractProject p) { return get(forward, p); } /** * @since 1.341 */ public List<Dependency> getUpstreamDependencies(AbstractProject p) { return get(backward, p); } private List<Dependency> get(Map<AbstractProject, List<DependencyGroup>> map, AbstractProject src) { List<DependencyGroup> v = map.get(src); if (v != null) { return Collections.<Dependency>unmodifiableList(v); } else { return Collections.emptyList(); } } /** * @deprecated since 1.341; use {@link #addDependency(Dependency)} */ @Deprecated public void addDependency(AbstractProject upstream, AbstractProject downstream) { addDependency(new Dependency(upstream, downstream)); } /** * Called during the dependency graph build phase to add a dependency edge. */ public void addDependency(Dependency dep) { if (built) { throw new IllegalStateException(); } add(forward, dep.getUpstreamProject(), dep); add(backward, dep.getDownstreamProject(), dep); } /** * @deprecated since 1.341 */ @Deprecated public void addDependency(AbstractProject upstream, Collection<? extends AbstractProject> downstream) { for (AbstractProject p : downstream) { addDependency(upstream, p); } } /** * @deprecated since 1.341 */ @Deprecated public void addDependency(Collection<? extends AbstractProject> upstream, AbstractProject downstream) { for (AbstractProject p : upstream) { addDependency(p, downstream); } } /** * Lists up {@link DependecyDeclarer} from the collection and let them * builds dependencies. */ public void addDependencyDeclarers(AbstractProject upstream, Collection<?> possibleDependecyDeclarers) { for (Object o : possibleDependecyDeclarers) { if (o instanceof DependecyDeclarer) { DependecyDeclarer dd = (DependecyDeclarer) o; dd.buildDependencyGraph(upstream, this); } } } /** * Returns true if a project has a non-direct dependency to another project. * <p> A non-direct dependency is a path of dependency "edge"s from the * source to the destination, where the length is greater than 1. */ public boolean hasIndirectDependencies(AbstractProject src, AbstractProject dst) { Set<AbstractProject> visited = new HashSet<AbstractProject>(); Stack<AbstractProject> queue = new Stack<AbstractProject>(); queue.addAll(getDownstream(src)); queue.remove(dst); while (!queue.isEmpty()) { AbstractProject p = queue.pop(); if (p == dst) { return true; } if (visited.add(p)) { queue.addAll(getDownstream(p)); } } return false; } /** * Gets all the direct and indirect upstream dependencies of the given * project. */ public Set<AbstractProject> getTransitiveUpstream(AbstractProject src) { return getTransitive(backward, src, true); } /** * Gets all the direct and indirect downstream dependencies of the given * project. */ public Set<AbstractProject> getTransitiveDownstream(AbstractProject src) { return getTransitive(forward, src, false); } private Set<AbstractProject> getTransitive(Map<AbstractProject, List<DependencyGroup>> direction, AbstractProject src, boolean up) { Set<AbstractProject> visited = new HashSet<AbstractProject>(); Stack<AbstractProject> queue = new Stack<AbstractProject>(); queue.add(src); while (!queue.isEmpty()) { AbstractProject p = queue.pop(); for (AbstractProject child : get(direction, p, up)) { if (visited.add(child)) { queue.add(child); } } } return visited; } private void add(Map<AbstractProject, List<DependencyGroup>> map, AbstractProject key, Dependency dep) { List<DependencyGroup> set = map.get(key); if (set == null) { set = new ArrayList<DependencyGroup>(); map.put(key, set); } for (ListIterator<DependencyGroup> it = set.listIterator(); it.hasNext();) { DependencyGroup d = it.next(); // Check for existing edge that connects the same two projects: if (d.getUpstreamProject() == dep.getUpstreamProject() && d.getDownstreamProject() == dep.getDownstreamProject()) { d.add(dep); return; } } // Otherwise add to list: set.add(new DependencyGroup(dep)); } private Map<AbstractProject, List<DependencyGroup>> finalize(Map<AbstractProject, List<DependencyGroup>> m) { for (Entry<AbstractProject, List<DependencyGroup>> e : m.entrySet()) { Collections.sort(e.getValue(), NAME_COMPARATOR); e.setValue(Collections.unmodifiableList(e.getValue())); } return Collections.unmodifiableMap(m); } /** * Margins between the project name and its bounding box. */ private static final int MARGIN = 4; private static final Comparator<Dependency> NAME_COMPARATOR = new Comparator<Dependency>() { public int compare(Dependency lhs, Dependency rhs) { int cmp = lhs.getUpstreamProject().getName().compareTo(rhs.getUpstreamProject().getName()); return cmp != 0 ? cmp : lhs.getDownstreamProject().getName().compareTo(rhs.getDownstreamProject().getName()); } }; public static final DependencyGraph EMPTY = new DependencyGraph(false); /** * Compare to Projects based on the topological order defined by this * Dependency Graph */ public int compare(AbstractProject o1, AbstractProject o2) { Set<AbstractProject> o1sdownstreams = getTransitiveDownstream(o1); Set<AbstractProject> o2sdownstreams = getTransitiveDownstream(o2); if (o1sdownstreams.contains(o2)) { if (o2sdownstreams.contains(o1)) { return 0; } else { return 1; } } else { if (o2sdownstreams.contains(o1)) { return -1; } else { return 0; } } } /** * Represents an edge in the dependency graph. * * @since 1.341 */ public static class Dependency { private AbstractProject upstream, downstream; public Dependency(AbstractProject upstream, AbstractProject downstream) { this.upstream = upstream; this.downstream = downstream; } public AbstractProject getUpstreamProject() { return upstream; } public AbstractProject getDownstreamProject() { return downstream; } /** * Decide whether build should be triggered and provide any Actions for * the build. Default implementation always returns true (for backward * compatibility), and adds no Actions. Subclasses may override to * control how/if the build is triggered. * * @param build Build of upstream project that just completed * @param listener For any error/log output * @param actions Add Actions for the triggered build to this list; * never null * @return True to trigger a build of the downstream project */ public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, List<Action> actions) { return true; } /** * Does this method point to itself? */ public boolean pointsItself() { return upstream == downstream; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Dependency that = (Dependency) obj; return this.upstream == that.upstream || this.downstream == that.downstream; } @Override public int hashCode() { int hash = 7; hash = 23 * hash + this.upstream.hashCode(); hash = 23 * hash + this.downstream.hashCode(); return hash; } } /** * Collect multiple dependencies between the same two projects. */ private static class DependencyGroup extends Dependency { private Set<Dependency> group = new LinkedHashSet<Dependency>(); DependencyGroup(Dependency first) { super(first.getUpstreamProject(), first.getDownstreamProject()); group.add(first); } void add(Dependency next) { group.add(next); } @Override public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, List<Action> actions) { List<Action> check = new ArrayList<Action>(); for (Dependency d : group) { if (d.shouldTriggerBuild(build, listener, check)) { actions.addAll(check); return true; } else { check.clear(); } } return false; } } }