001package org.apache.maven.plugins.enforcer;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.Collections;
023import java.util.List;
024
025import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
026import org.apache.maven.enforcer.rule.api.EnforcerRule;
027import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
028import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
029import org.apache.maven.plugins.enforcer.utils.ArtifactMatcher;
030import org.apache.maven.project.MavenProject;
031import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
032import org.apache.maven.shared.dependency.graph.DependencyNode;
033import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyGraphBuilder;
034import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
035import org.codehaus.plexus.logging.console.ConsoleLogger;
036
037/**
038 * This rule bans all transitive dependencies. There is a configuration option to exclude certain artifacts from being
039 * checked.
040 * 
041 * @author Jakub Senko
042 */
043public class BanTransitiveDependencies
044    extends AbstractNonCacheableEnforcerRule
045    implements EnforcerRule
046{
047
048    private EnforcerRuleHelper helper;
049
050    /**
051     * Specify the dependencies that will be ignored. This can be a list of artifacts in the format
052     * <code>groupId[:artifactId][:version][:type][:scope]</code>. Wildcard '*' can be used to in place of specific
053     * section (ie group:*:1.0 will match both 'group:artifact:1.0' and 'group:anotherArtifact:1.0') <br>
054     * You can override this patterns by using includes. Version is a string representing standard maven version range.
055     * Empty patterns will be ignored.
056     */
057    private List<String> excludes;
058
059    /**
060     * Specify the dependencies that will be checked. These are exceptions to excludes intended for more convenient and
061     * finer settings. This can be a list of artifacts in the format
062     * <code>groupId[:artifactId][:version][:type][:scope]</code>. Wildcard '*' can be used to in place of specific
063     * section (ie group:*:1.0 will match both 'group:artifact:1.0' and 'group:anotherArtifact:1.0') <br>
064     * Version is a string representing standard maven version range. Empty patterns will be ignored.
065     */
066    private List<String> includes;
067
068    /**
069     * Searches dependency tree recursively for transitive dependencies that are not excluded, while generating nice
070     * info message along the way.
071     * 
072     * @throws InvalidVersionSpecificationException
073     */
074    private static boolean searchTree( DependencyNode node, int level, ArtifactMatcher excludes, StringBuilder message )
075        throws InvalidVersionSpecificationException
076    {
077
078        List<DependencyNode> children = node.getChildren();
079
080        /*
081         * if the node is deeper than direct dependency and is empty, it is transitive.
082         */
083        boolean hasTransitiveDependencies = level > 1;
084
085        boolean excluded = false;
086
087        /*
088         * holds recursive message from children, will be appended to current message if this node has any transitive
089         * descendants if message is null, don't generate recursive message.
090         */
091        StringBuilder messageFromChildren = message == null ? null : new StringBuilder();
092
093        if ( excludes.match( node.getArtifact() ) )
094        {
095            // is excluded, we don't care about descendants
096            excluded = true;
097            hasTransitiveDependencies = false;
098        }
099        else
100        {
101            for ( DependencyNode childNode : children )
102            {
103                /*
104                 * if any of the children has transitive d. so does the parent
105                 */
106                hasTransitiveDependencies =
107                    ( searchTree( childNode, level + 1, excludes, messageFromChildren ) || hasTransitiveDependencies );
108            }
109        }
110
111        if ( ( excluded || hasTransitiveDependencies ) && message != null ) // then generate message
112        {
113            for ( int i = 0; i < level; i++ )
114            {
115                message.append( "   " );
116            }
117
118            message.append( node.getArtifact() );
119
120            if ( excluded )
121            {
122                message.append( " [excluded]\n" );
123            }
124
125            if ( hasTransitiveDependencies )
126            {
127                if ( level == 1 )
128                {
129                    message.append( " has transitive dependencies:" );
130                }
131
132                message.append( "\n" ).append( messageFromChildren );
133            }
134        }
135
136        return hasTransitiveDependencies;
137    }
138
139    public void execute( EnforcerRuleHelper helper )
140        throws EnforcerRuleException
141    {
142        this.helper = helper;
143
144        if ( excludes == null )
145        {
146            excludes = Collections.emptyList();
147        }
148        if ( includes == null )
149        {
150            includes = Collections.emptyList();
151        }
152
153        final ArtifactMatcher exclusions = new ArtifactMatcher( excludes, includes );
154
155        DependencyNode rootNode = null;
156
157        try
158        {
159            MavenProject project = (MavenProject) helper.evaluate( "${project}" );
160            rootNode = createDependencyGraphBuilder().buildDependencyGraph( project, null );
161        }
162        catch ( Exception e )
163        {
164            throw new EnforcerRuleException( "Error: Could not construct dependency tree.", e );
165        }
166
167        String message = getMessage();
168        StringBuilder generatedMessage = null;
169        if ( message == null )
170        {
171            generatedMessage = new StringBuilder();
172        }
173
174        try
175        {
176            if ( searchTree( rootNode, 0, exclusions, generatedMessage ) )
177            {
178                throw new EnforcerRuleException( message == null ? generatedMessage.toString() : message );
179            }
180        }
181        catch ( InvalidVersionSpecificationException e )
182        {
183            throw new EnforcerRuleException( "Error: Invalid version range.", e );
184        }
185
186    }
187
188    private DependencyGraphBuilder createDependencyGraphBuilder()
189        throws ComponentLookupException
190    {
191        // CHECKSTYLE_OFF: LineLength
192        DefaultDependencyGraphBuilder builder =
193            (DefaultDependencyGraphBuilder) helper.getContainer().lookup( DependencyGraphBuilder.class.getCanonicalName(),
194                                                                          "default" );
195        // CHECKSTYLE_ON: LineLength
196
197        builder.enableLogging( new ConsoleLogger( ConsoleLogger.LEVEL_DISABLED, "DefaultDependencyGraphBuilder" ) );
198
199        return builder;
200    }
201
202}