001    /**
002     * Copyright (C) 2009 Progress Software, Inc.
003     * http://fusesource.com
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *    http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.kuali.maven.plugins.graph;
018    
019    import java.io.File;
020    import java.util.ArrayList;
021    import java.util.List;
022    
023    import org.apache.maven.artifact.Artifact;
024    import org.apache.maven.artifact.factory.ArtifactFactory;
025    import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
026    import org.apache.maven.artifact.repository.ArtifactRepository;
027    import org.apache.maven.artifact.resolver.ArtifactCollector;
028    import org.apache.maven.artifact.resolver.ArtifactResolver;
029    import org.apache.maven.plugin.AbstractMojo;
030    import org.apache.maven.project.MavenProject;
031    import org.apache.maven.shared.dependency.tree.DependencyNode;
032    import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
033    import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
034    import org.kuali.maven.plugins.graph.collector.DependencyNodeTokenCollector;
035    import org.kuali.maven.plugins.graph.collector.TokenCollector;
036    import org.kuali.maven.plugins.graph.dot.Dot;
037    import org.kuali.maven.plugins.graph.dot.GraphException;
038    import org.kuali.maven.plugins.graph.dot.GraphHelper;
039    import org.kuali.maven.plugins.graph.dot.StringGenerator;
040    import org.kuali.maven.plugins.graph.dot.edge.EdgeHandler;
041    import org.kuali.maven.plugins.graph.filter.ArtifactFilterWrapper;
042    import org.kuali.maven.plugins.graph.filter.DependencyNodeFilterWrapper;
043    import org.kuali.maven.plugins.graph.filter.DepthFilter;
044    import org.kuali.maven.plugins.graph.filter.Filter;
045    import org.kuali.maven.plugins.graph.filter.Filters;
046    import org.kuali.maven.plugins.graph.filter.MatchCondition;
047    import org.kuali.maven.plugins.graph.filter.NodeFilter;
048    import org.kuali.maven.plugins.graph.filter.NodeFilterChain;
049    import org.kuali.maven.plugins.graph.filter.ReverseNodeFilter;
050    import org.kuali.maven.plugins.graph.pojo.Direction;
051    import org.kuali.maven.plugins.graph.pojo.DotContext;
052    import org.kuali.maven.plugins.graph.pojo.Edge;
053    import org.kuali.maven.plugins.graph.pojo.Graph;
054    import org.kuali.maven.plugins.graph.pojo.GraphNode;
055    import org.kuali.maven.plugins.graph.pojo.MavenContext;
056    import org.kuali.maven.plugins.graph.tree.Node;
057    import org.kuali.maven.plugins.graph.tree.TreeHelper;
058    import org.kuali.maven.plugins.graph.tree.TreeMetaData;
059    
060    /**
061     *
062     */
063    public abstract class BaseMojo extends AbstractMojo {
064        Filters filters = new Filters();
065    
066        /**
067         * @required
068         * @readonly
069         * @parameter expression="${project}"
070         * @since 1.0
071         */
072        private MavenProject project;
073    
074        /**
075         * @required
076         * @readonly
077         * @parameter expression="${localRepository}"
078         * @since 1.0
079         */
080        private ArtifactRepository localRepository;
081    
082        /**
083         * @required
084         * @component
085         * @since 1.0
086         */
087        private ArtifactResolver artifactResolver;
088    
089        /**
090         * @required
091         * @readonly
092         * @component
093         * @since 1.0
094         */
095        private ArtifactFactory artifactFactory;
096    
097        /**
098         * @required
099         * @readonly
100         * @component
101         * @since 1.0
102         */
103        private ArtifactMetadataSource artifactMetadataSource;
104    
105        /**
106         * @required
107         * @readonly
108         * @component
109         */
110        private ArtifactCollector artifactCollector;
111    
112        /**
113         * @required
114         * @readonly
115         * @component
116         * @since 1.0
117         */
118        private DependencyTreeBuilder treeBuilder;
119    
120        /**
121         * The title for the graph
122         *
123         * @parameter expression="${graph.title}" default-value="Dependency Graph for ${project.name}"
124         */
125        private String title;
126    
127        /**
128         * Set to true to retain the .dot file used to draw the graph
129         *
130         * @parameter expression="${graph.keepDotFile}" default-value="false"
131         */
132        private boolean keepDotFile;
133    
134        /**
135         * Set to false to show only the dependencies for the current project
136         *
137         * @parameter expression="${graph.transitive}" default-value="true"
138         */
139        private boolean transitive;
140    
141        /**
142         * <p>
143         * Comma delimited list of artifact patterns to include. <code>Includes</code> works "bottom up" and can be
144         * overridden by <code>excludes</code>. If an artifact matches the <code>includes</code> criteria, it, and all of
145         * the dependencies in the direct path from it back to the root of the dependency tree are displayed.
146         * </p>
147         *
148         * The pattern syntax has the form:
149         *
150         * <pre>
151         * [groupId]:[artifactId]:[type]:[classifier]:[version]
152         * </pre>
153         *
154         * <p>
155         * Each pattern segment is optional and supports <code>*</code> wildcards. An empty pattern segment is treated as a
156         * wildcard.
157         * </p>
158         *
159         * @parameter expression="${graph.includes}"
160         */
161        private String includes;
162    
163        /**
164         * <p>
165         * Comma delimited list of artifact patterns to exclude. <code>Excludes</code> overrides <code>includes</code> and
166         * works "top down". If a dependency matches the <code>excludes</code> criteria, it, and all dependencies below it,
167         * are removed from the display. If not provided, no artifacts are excluded.
168         * </p>
169         *
170         * The pattern syntax has the form:
171         *
172         * <pre>
173         * [groupId]:[artifactId]:[type]:[classifier]:[version]
174         * </pre>
175         *
176         * <p>
177         * Each pattern segment is optional and supports <code>*</code> wildcards. An empty pattern segment is treated as a
178         * wildcard.
179         * </p>
180         *
181         * @parameter expression="${graph.excludes}"
182         */
183        private String excludes;
184    
185        /**
186         * <p>
187         * Comma delimited list of dependency qualifiers used for "hiding" artifacts. <code>Hide</code> overrides
188         * <code>show</code> and works "top down". If a dependency matches the <code>hide</code> criteria, it, and
189         * dependencies below it, are removed from the display. If not provided, no dependencies are hidden.
190         * </p>
191         *
192         * The pattern syntax has the form:
193         *
194         * <pre>
195         * [scope]:[optional|required]:[state]
196         * </pre>
197         *
198         * Scopes: <code>compile,provided,runtime,test,system,import</code><br>
199         * States: <code>normal,conflict,cyclic,duplicate</code><br>
200         *
201         * <p>
202         * Each pattern segment is optional and supports <code>*</code> wildcards. An empty pattern segment is treated as a
203         * wildcard.
204         * </p>
205         *
206         * @parameter expression="${graph.hide}"
207         */
208        private String hide;
209    
210        /**
211         * <p>
212         * Comma delimited list of dependency qualifiers used for "showing" artifacts. <code>Show</code> works "bottom up"
213         * and can be overridden by <code>hide</code>. If a dependency matches the <code>show</code> criteria, it, and all
214         * of the dependencies in the direct path from it back to the root of the dependency tree are displayed.
215         * </p>
216         *
217         * The pattern syntax has the form:
218         *
219         * <pre>
220         * [scope]:[optional|required]:[state]
221         * </pre>
222         *
223         * Scopes: <code>compile,provided,runtime,test,system,import</code><br>
224         * States: <code>normal,conflict,cyclic,duplicate</code><br>
225         *
226         * <p>
227         * Each pattern segment is optional and supports <code>*</code> wildcards. An empty pattern segment is treated as a
228         * wildcard.
229         * </p>
230         *
231         * @parameter expression="${graph.show}"
232         */
233        private String show;
234    
235        /**
236         * The direction for the graph layout. Top to bottom, left to right, bottom to top, and right to left.
237         *
238         * @parameter expression="${graph.direction}" default-value="TB"
239         */
240        private Direction direction;
241    
242        @Override
243        public void execute() {
244            try {
245                String content = getDotFileContent(title, direction);
246                Dot dot = new Dot();
247                DotContext context = dot.getDotContext(getFile(), content, keepDotFile);
248                dot.execute(context);
249            } catch (Exception e) {
250                e.printStackTrace();
251            }
252        }
253    
254        protected abstract File getFile();
255    
256        protected abstract EdgeHandler getEdgeHandler();
257    
258        /**
259         * Restricts the depth of the dependency tree. To show only the dependencies of your project, set this to 1. To show
260         * the dependencies of your project and their direct dependencies, set this to 2.
261         *
262         * @parameter expression="${graph.depth}" default-value="-1"
263         */
264        private int depth;
265    
266        protected DepthFilter<MavenContext> getDepthFilter() {
267            int maxDepth = transitive ? DepthFilter.INFINITE : 1;
268            maxDepth = depth >= 0 ? depth : maxDepth;
269            return new DepthFilter<MavenContext>(maxDepth);
270        }
271    
272        protected void preProcess(Node<MavenContext> node) {
273            // do nothing by default
274        }
275    
276        protected void postProcess(Node<MavenContext> node, List<GraphNode> nodes, List<Edge> edges) {
277            // do nothing by default
278        }
279    
280        protected String getDotFileContent(String title, Direction direction) {
281            TreeHelper helper = new TreeHelper();
282            DependencyNode mavenTree = getMavenTree();
283            Node<MavenContext> nodeTree = helper.getTree(mavenTree);
284            preProcess(nodeTree);
285            helper.sanitize(nodeTree);
286            TreeMetaData md = helper.getMetaData(nodeTree);
287            helper.show(md);
288            helper.include(nodeTree, getIncludeFilter());
289            helper.exclude(nodeTree, getExcludeFilter());
290            List<GraphNode> nodes = helper.getGraphNodes(nodeTree);
291            EdgeHandler handler = getEdgeHandler();
292            List<Edge> edges = helper.getEdges(nodeTree, handler);
293            postProcess(nodeTree, nodes, edges);
294            helper.show(nodes, edges);
295            Graph graph = new GraphHelper().getGraph(title, direction, nodes, edges);
296            return new StringGenerator().getString(graph);
297        }
298    
299        protected NodeFilter<MavenContext> getShowFilter() {
300            TokenCollector<DependencyNode> collector = new DependencyNodeTokenCollector();
301            Filter<DependencyNode> filter = filters.getIncludePatternFilter(getShow(), collector);
302            return new DependencyNodeFilterWrapper(filter);
303        }
304    
305        protected NodeFilter<MavenContext> getHideFilter() {
306            TokenCollector<DependencyNode> collector = new DependencyNodeTokenCollector();
307            Filter<DependencyNode> filter = filters.getExcludePatternFilter(getHide(), collector);
308            return new DependencyNodeFilterWrapper(filter);
309        }
310    
311        protected NodeFilter<MavenContext> getIncludeFilter() {
312            TokenCollector<Artifact> collector = new org.kuali.maven.plugins.graph.collector.ArtifactIdTokenCollector();
313            Filter<Artifact> filter = filters.getIncludePatternFilter(getIncludes(), collector);
314            ArtifactFilterWrapper artifactFilter = new ArtifactFilterWrapper(filter);
315            List<NodeFilter<MavenContext>> filters = new ArrayList<NodeFilter<MavenContext>>();
316            NodeFilter<MavenContext> artifactQualifierFilter = getShowFilter();
317            filters.add(artifactQualifierFilter);
318            filters.add(artifactFilter);
319            return new NodeFilterChain<MavenContext>(filters, MatchCondition.ALL, true);
320        }
321    
322        protected NodeFilter<MavenContext> getExcludeFilter() {
323            TokenCollector<Artifact> collector = new org.kuali.maven.plugins.graph.collector.ArtifactIdTokenCollector();
324            Filter<Artifact> filter = filters.getExcludePatternFilter(getExcludes(), collector);
325            ArtifactFilterWrapper artifactFilter = new ArtifactFilterWrapper(filter);
326            ReverseNodeFilter<MavenContext> depthFilter = new ReverseNodeFilter<MavenContext>(getDepthFilter());
327            NodeFilter<MavenContext> artifactQualifierFilter = getHideFilter();
328            List<NodeFilter<MavenContext>> filters = new ArrayList<NodeFilter<MavenContext>>();
329            filters.add(artifactQualifierFilter);
330            filters.add(artifactFilter);
331            filters.add(depthFilter);
332            return new NodeFilterChain<MavenContext>(filters, MatchCondition.ANY, false);
333        }
334    
335        protected DependencyNode getMavenTree() {
336            try {
337                return getTreeBuilder().buildDependencyTree(project, localRepository, artifactFactory,
338                        artifactMetadataSource, null, artifactCollector);
339            } catch (DependencyTreeBuilderException e) {
340                throw new GraphException(e);
341            }
342        }
343    
344        public String getIncludes() {
345            return includes;
346        }
347    
348        public void setIncludes(String includes) {
349            this.includes = includes;
350        }
351    
352        public String getExcludes() {
353            return excludes;
354        }
355    
356        public void setExcludes(String excludes) {
357            this.excludes = excludes;
358        }
359    
360        public String getHide() {
361            return hide;
362        }
363    
364        public void setHide(String hide) {
365            this.hide = hide;
366        }
367    
368        public String getShow() {
369            return show;
370        }
371    
372        public void setShow(String show) {
373            this.show = show;
374        }
375    
376        public String getTitle() {
377            return title;
378        }
379    
380        public void setTitle(String title) {
381            this.title = title;
382        }
383    
384        public boolean isKeepDotFile() {
385            return keepDotFile;
386        }
387    
388        public void setKeepDotFile(boolean keepDotFile) {
389            this.keepDotFile = keepDotFile;
390        }
391    
392        public boolean isTransitive() {
393            return transitive;
394        }
395    
396        public void setTransitive(boolean transitive) {
397            this.transitive = transitive;
398        }
399    
400        public Direction getDirection() {
401            return direction;
402        }
403    
404        public void setDirection(Direction direction) {
405            this.direction = direction;
406        }
407    
408        public int getDepth() {
409            return depth;
410        }
411    
412        public void setDepth(int depth) {
413            this.depth = depth;
414        }
415    
416        public MavenProject getProject() {
417            return project;
418        }
419    
420        public ArtifactRepository getLocalRepository() {
421            return localRepository;
422        }
423    
424        public ArtifactResolver getArtifactResolver() {
425            return artifactResolver;
426        }
427    
428        public ArtifactFactory getArtifactFactory() {
429            return artifactFactory;
430        }
431    
432        public ArtifactMetadataSource getArtifactMetadataSource() {
433            return artifactMetadataSource;
434        }
435    
436        public ArtifactCollector getArtifactCollector() {
437            return artifactCollector;
438        }
439    
440        public DependencyTreeBuilder getTreeBuilder() {
441            return treeBuilder;
442        }
443    
444    }