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    @Override
140    public void execute( EnforcerRuleHelper helper )
141        throws EnforcerRuleException
142    {
143        this.helper = helper;
144
145        if ( excludes == null )
146        {
147            excludes = Collections.emptyList();
148        }
149        if ( includes == null )
150        {
151            includes = Collections.emptyList();
152        }
153
154        final ArtifactMatcher exclusions = new ArtifactMatcher( excludes, includes );
155
156        DependencyNode rootNode = null;
157
158        try
159        {
160            MavenProject project = (MavenProject) helper.evaluate( "${project}" );
161            rootNode = createDependencyGraphBuilder().buildDependencyGraph( project, null );
162        }
163        catch ( Exception e )
164        {
165            throw new EnforcerRuleException( "Error: Could not construct dependency tree.", e );
166        }
167
168        String message = getMessage();
169        StringBuilder generatedMessage = null;
170        if ( message == null )
171        {
172            generatedMessage = new StringBuilder();
173        }
174
175        try
176        {
177            if ( searchTree( rootNode, 0, exclusions, generatedMessage ) )
178            {
179                throw new EnforcerRuleException( message == null ? generatedMessage.toString() : message );
180            }
181        }
182        catch ( InvalidVersionSpecificationException e )
183        {
184            throw new EnforcerRuleException( "Error: Invalid version range.", e );
185        }
186
187    }
188
189    private DependencyGraphBuilder createDependencyGraphBuilder()
190        throws ComponentLookupException
191    {
192        // CHECKSTYLE_OFF: LineLength
193        DefaultDependencyGraphBuilder builder =
194            (DefaultDependencyGraphBuilder) helper.getContainer().lookup( DependencyGraphBuilder.class.getCanonicalName(),
195                                                                          "default" );
196        // CHECKSTYLE_ON: LineLength
197
198        builder.enableLogging( new ConsoleLogger( ConsoleLogger.LEVEL_DISABLED, "DefaultDependencyGraphBuilder" ) );
199
200        return builder;
201    }
202
203}