DependencyGraph.java 14.5 KB
Newer Older
K
kohsuke 已提交
1 2
/*
 * The MIT License
3
 *
4 5
 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
 * Martin Eigenbrodt. Seiji Sogabe, Alan Harder
6
 *
K
kohsuke 已提交
7 8 9 10 11 12
 * 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:
13
 *
K
kohsuke 已提交
14 15
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
16
 *
K
kohsuke 已提交
17 18 19 20 21 22 23 24
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
25 26
package hudson.model;

27
import jenkins.model.DependencyDeclarer;
28
import com.google.common.collect.ImmutableList;
29
import hudson.security.ACL;
30
import jenkins.model.Jenkins;
31
import org.acegisecurity.context.SecurityContext;
32
import org.acegisecurity.context.SecurityContextHolder;
33

34 35 36 37 38
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
39
import java.util.HashSet;
40
import java.util.LinkedHashSet;
41
import java.util.List;
42
import java.util.ListIterator;
43
import java.util.Map;
44
import java.util.Map.Entry;
K
kohsuke 已提交
45 46
import java.util.Set;
import java.util.Stack;
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65

/**
 * 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.
 *
66
 * @see Jenkins#getDependencyGraph()
67 68
 * @author Kohsuke Kawaguchi
 */
69
public class DependencyGraph implements Comparator<AbstractProject> {
70

71 72
    private Map<AbstractProject, List<DependencyGroup>> forward = new HashMap<AbstractProject, List<DependencyGroup>>();
    private Map<AbstractProject, List<DependencyGroup>> backward = new HashMap<AbstractProject, List<DependencyGroup>>();
73

74
    private transient Map<Class<?>, Object> computationalData;
75 76 77 78 79 80 81

    private boolean built;

    /**
     * Builds the dependency graph.
     */
    public DependencyGraph() {
82 83 84
    }
    
    public void build() {
85
        // Set full privileges while computing to avoid missing any projects the current user cannot see.
86
        SecurityContext saveCtx = ACL.impersonate(ACL.SYSTEM);
87
        try {
88
            this.computationalData = new HashMap<Class<?>, Object>();
89
            for( AbstractProject p : getAllProjects() )
90
                p.buildDependencyGraph(this);
91

92 93
            forward = finalize(forward);
            backward = finalize(backward);
94

95
            this.computationalData = null;
96 97
            built = true;
        } finally {
98
            SecurityContextHolder.setContext(saveCtx);
99
        }
100
    }
101 102 103 104
    
    Collection<AbstractProject> getAllProjects() {
        return Jenkins.getInstance().getAllItems(AbstractProject.class);
    }
105 106 107 108 109 110 111 112 113

    /**
     * Special constructor for creating an empty graph
     */
    private DependencyGraph(boolean dummy) {
        forward = backward = Collections.emptyMap();
        built = true;
    }

114 115 116 117 118 119 120
    /**
     * Adds data which is useful for the time when the dependency graph is built up.
     * All this data will be cleaned once the dependency graph creation has finished.
     */
    public <T> void putComputationalData(Class<T> key, T value) {
        this.computationalData.put(key, value);
    }
121

122 123 124 125 126 127 128 129
    /**
     * Gets temporary data which is needed for building up the dependency graph.
     */
    public <T> T getComputationalData(Class<T> key) {
        @SuppressWarnings("unchecked")
        T result = (T) this.computationalData.get(key);
        return result;
    }
130

131 132 133 134 135 136 137
    /**
     * 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) {
138
        return get(forward,p,false);
139 140 141 142 143 144 145 146 147
    }

    /**
     * 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) {
148 149 150
        return get(backward,p,true);
    }

151 152
    private List<AbstractProject> get(Map<AbstractProject, List<DependencyGroup>> map, AbstractProject src, boolean up) {
        List<DependencyGroup> v = map.get(src);
153 154
        if(v==null) return Collections.emptyList();
        List<AbstractProject> result = new ArrayList<AbstractProject>(v.size());
155
        for (DependencyGroup d : v) result.add(up ? d.getUpstreamProject() : d.getDownstreamProject());
156 157 158 159 160 161 162 163 164 165 166 167 168 169
        return result;
    }

    /**
     * @since 1.341
     */
    public List<Dependency> getDownstreamDependencies(AbstractProject p) {
        return get(forward,p);
    }

    /**
     * @since 1.341
     */
    public List<Dependency> getUpstreamDependencies(AbstractProject p) {
170 171 172
        return get(backward,p);
    }

173 174
    private List<Dependency> get(Map<AbstractProject, List<DependencyGroup>> map, AbstractProject src) {
        List<DependencyGroup> v = map.get(src);
175 176 177 178 179 180 181 182 183 184
        if(v==null) {
            return ImmutableList.of();
        } else {
            ImmutableList.Builder<Dependency> builder = ImmutableList.builder();
            for (DependencyGroup dependencyGroup : v) {
                builder.addAll(dependencyGroup.getGroup());
            }
            return builder.build();
        }

185 186 187
    }

    /**
188
     * @deprecated since 1.341; use {@link #addDependency(Dependency)}
189
     */
190
    @Deprecated
191
    public void addDependency(AbstractProject upstream, AbstractProject downstream) {
192 193 194 195 196 197 198
        addDependency(new Dependency(upstream,downstream));
    }

    /**
     * Called during the dependency graph build phase to add a dependency edge.
     */
    public void addDependency(Dependency dep) {
199 200
        if(built)
            throw new IllegalStateException();
201 202
        add(forward,dep.getUpstreamProject(),dep);
        add(backward,dep.getDownstreamProject(),dep);
203 204
    }

205 206 207 208
    /**
     * @deprecated since 1.341
     */
    @Deprecated
209 210 211
    public void addDependency(AbstractProject upstream, Collection<? extends AbstractProject> downstream) {
        for (AbstractProject p : downstream)
            addDependency(upstream,p);
212 213
    }

214 215 216 217
    /**
     * @deprecated since 1.341
     */
    @Deprecated
218 219 220
    public void addDependency(Collection<? extends AbstractProject> upstream, AbstractProject downstream) {
        for (AbstractProject p : upstream)
            addDependency(p,downstream);
221 222
    }

223
    /**
224
     * Lists up {@link DependencyDeclarer} from the collection and let them builds dependencies.
225
     */
226
    public void addDependencyDeclarers(AbstractProject upstream, Collection<?> possibleDependecyDeclarers) {
227
        for (Object o : possibleDependecyDeclarers) {
228 229
            if (o instanceof DependencyDeclarer) {
                DependencyDeclarer dd = (DependencyDeclarer) o;
230
                dd.buildDependencyGraph(upstream,this);
231 232 233 234
            }
        }
    }

K
kohsuke 已提交
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
    /**
     * 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;
    }

259 260 261 262
    /**
     * Gets all the direct and indirect upstream dependencies of the given project.
     */
    public Set<AbstractProject> getTransitiveUpstream(AbstractProject src) {
263
        return getTransitive(backward,src,true);
264 265 266 267 268 269
    }

    /**
     * Gets all the direct and indirect downstream dependencies of the given project.
     */
    public Set<AbstractProject> getTransitiveDownstream(AbstractProject src) {
270
        return getTransitive(forward,src,false);
271 272
    }

273
    private Set<AbstractProject> getTransitive(Map<AbstractProject, List<DependencyGroup>> direction, AbstractProject src, boolean up) {
274 275 276 277 278 279 280 281
        Set<AbstractProject> visited = new HashSet<AbstractProject>();
        Stack<AbstractProject> queue = new Stack<AbstractProject>();

        queue.add(src);

        while(!queue.isEmpty()) {
            AbstractProject p = queue.pop();

282
            for (AbstractProject child : get(direction,p,up)) {
283 284 285 286 287 288 289 290
                if(visited.add(child))
                    queue.add(child);
            }
        }

        return visited;
    }

291 292
    private void add(Map<AbstractProject, List<DependencyGroup>> map, AbstractProject key, Dependency dep) {
        List<DependencyGroup> set = map.get(key);
293
        if(set==null) {
294
            set = new ArrayList<DependencyGroup>();
295
            map.put(key,set);
296
        }
297 298
        for (ListIterator<DependencyGroup> it = set.listIterator(); it.hasNext();) {
            DependencyGroup d = it.next();
299
            // Check for existing edge that connects the same two projects:
300
            if (d.getUpstreamProject()==dep.getUpstreamProject() && d.getDownstreamProject()==dep.getDownstreamProject()) {
301
                d.add(dep);
302 303 304 305
                return;
            }
        }
        // Otherwise add to list:
306
        set.add(new DependencyGroup(dep));
307 308
    }

309 310
    private Map<AbstractProject, List<DependencyGroup>> finalize(Map<AbstractProject, List<DependencyGroup>> m) {
        for (Entry<AbstractProject, List<DependencyGroup>> e : m.entrySet()) {
311 312 313 314 315 316
            Collections.sort( e.getValue(), NAME_COMPARATOR );
            e.setValue( Collections.unmodifiableList(e.getValue()) );
        }
        return Collections.unmodifiableMap(m);
    }

317 318
    private static final Comparator<DependencyGroup> NAME_COMPARATOR = new Comparator<DependencyGroup>() {
        public int compare(DependencyGroup lhs, DependencyGroup rhs) {
319 320
            int cmp = lhs.getUpstreamProject().getName().compareTo(rhs.getUpstreamProject().getName());
            return cmp != 0 ? cmp : lhs.getDownstreamProject().getName().compareTo(rhs.getDownstreamProject().getName());
321 322 323 324
        }
    };

    public static final DependencyGraph EMPTY = new DependencyGraph(false);
325 326

    /**
K
kohsuke 已提交
327
     * Compare to Projects based on the topological order defined by this Dependency Graph
K
TAB->WS  
kohsuke 已提交
328
     */
K
kohsuke 已提交
329 330 331 332
    public int compare(AbstractProject o1, AbstractProject o2) {
        Set<AbstractProject> o1sdownstreams = getTransitiveDownstream(o1);
        Set<AbstractProject> o2sdownstreams = getTransitiveDownstream(o2);
        if (o1sdownstreams.contains(o2)) {
333
            if (o2sdownstreams.contains(o1)) return 0; else return 1;
K
kohsuke 已提交
334
        } else {
335 336
            if (o2sdownstreams.contains(o1)) return -1; else return 0;
        }
K
kohsuke 已提交
337
    }
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359

    /**
     * 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;
        }

        /**
360 361 362
         * 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.
363 364
         * @param build Build of upstream project that just completed
         * @param listener For any error/log output
365
         * @param actions Add Actions for the triggered build to this list; never null
366 367
         * @return True to trigger a build of the downstream project
         */
368 369
        public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener,
                                          List<Action> actions) {
370 371
            return true;
        }
372

373 374 375 376 377 378 379
        /**
         * Does this method point to itself?
         */
        public boolean pointsItself() {
            return upstream==downstream;
        }

380 381 382 383 384
        @Override
        public boolean equals(Object obj) {
            if (obj == null) return false;
            if (getClass() != obj.getClass()) return false;

K
kohsuke 已提交
385 386
            final Dependency that = (Dependency) obj;
            return this.upstream == that.upstream || this.downstream == that.downstream;
387 388 389 390 391
        }

        @Override
        public int hashCode() {
            int hash = 7;
K
kohsuke 已提交
392 393
            hash = 23 * hash + this.upstream.hashCode();
            hash = 23 * hash + this.downstream.hashCode();
394 395
            return hash;
        }
396
    }
397 398 399 400

    /**
     * Collect multiple dependencies between the same two projects.
     */
401
    private static class DependencyGroup {
402
        private Set<Dependency> group = new LinkedHashSet<Dependency>();
403

404
        DependencyGroup(Dependency first) {
405 406
            this.upstream = first.getUpstreamProject();
            this.downstream= first.getDownstreamProject();
407 408 409
            group.add(first);
        }

410
        private void add(Dependency next) {
411 412 413
            group.add(next);
        }

414 415 416
        public Set<Dependency> getGroup() {
            return group;
        }
417 418 419 420 421 422 423 424 425

        private AbstractProject upstream, downstream;

        public AbstractProject getUpstreamProject() {
            return upstream;
        }

        public AbstractProject getDownstreamProject() {
            return downstream;
426 427
        }
    }
428
}