View Javadoc

1   package org.apache.maven.plugins.enforcer;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.Collections;
23  import java.util.List;
24  
25  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
26  import org.apache.maven.enforcer.rule.api.EnforcerRule;
27  import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
28  import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
29  import org.apache.maven.plugins.enforcer.utils.ArtifactMatcher;
30  import org.apache.maven.project.MavenProject;
31  import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
32  import org.apache.maven.shared.dependency.graph.DependencyNode;
33  import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyGraphBuilder;
34  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
35  import org.codehaus.plexus.logging.console.ConsoleLogger;
36  
37  /**
38   * This rule bans all transitive dependencies. There is a configuration option to exclude certain artifacts from being
39   * checked.
40   * 
41   * @author Jakub Senko
42   */
43  public class BanTransitiveDependencies
44      extends AbstractNonCacheableEnforcerRule
45      implements EnforcerRule
46  {
47  
48      private EnforcerRuleHelper helper;
49  
50      /**
51       * Specify the dependencies that will be ignored. This can be a list of artifacts in the format
52       * <code>groupId[:artifactId][:version][:type][:scope]</code>. Wildcard '*' can be used to in place of specific
53       * section (ie group:*:1.0 will match both 'group:artifact:1.0' and 'group:anotherArtifact:1.0') <br>
54       * You can override this patterns by using includes. Version is a string representing standard maven version range.
55       * Empty patterns will be ignored.
56       */
57      private List<String> excludes;
58  
59      /**
60       * Specify the dependencies that will be checked. These are exceptions to excludes intended for more convenient and
61       * finer settings. This can be a list of artifacts in the format
62       * <code>groupId[:artifactId][:version][:type][:scope]</code>. Wildcard '*' can be used to in place of specific
63       * section (ie group:*:1.0 will match both 'group:artifact:1.0' and 'group:anotherArtifact:1.0') <br>
64       * Version is a string representing standard maven version range. Empty patterns will be ignored.
65       */
66      private List<String> includes;
67  
68      /**
69       * Searches dependency tree recursively for transitive dependencies that are not excluded, while generating nice
70       * info message along the way.
71       * 
72       * @throws InvalidVersionSpecificationException
73       */
74      private static boolean searchTree( DependencyNode node, int level, ArtifactMatcher excludes, StringBuilder message )
75          throws InvalidVersionSpecificationException
76      {
77  
78          List<DependencyNode> children = node.getChildren();
79  
80          /*
81           * if the node is deeper than direct dependency and is empty, it is transitive.
82           */
83          boolean hasTransitiveDependencies = level > 1;
84  
85          boolean excluded = false;
86  
87          /*
88           * holds recursive message from children, will be appended to current message if this node has any transitive
89           * descendants if message is null, don't generate recursive message.
90           */
91          StringBuilder messageFromChildren = message == null ? null : new StringBuilder();
92  
93          if ( excludes.match( node.getArtifact() ) )
94          {
95              // is excluded, we don't care about descendants
96              excluded = true;
97              hasTransitiveDependencies = false;
98          }
99          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         DefaultDependencyGraphBuilder builder =
192             (DefaultDependencyGraphBuilder) helper.getContainer().lookup( DependencyGraphBuilder.class.getCanonicalName(),
193                                                                           "default" );
194 
195         builder.enableLogging( new ConsoleLogger( ConsoleLogger.LEVEL_DISABLED, "DefaultDependencyGraphBuilder" ) );
196 
197         return builder;
198     }
199 
200 }