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.ArrayList;
23  import java.util.Collections;
24  import java.util.LinkedHashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.maven.artifact.Artifact;
29  import org.apache.maven.artifact.factory.ArtifactFactory;
30  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
31  import org.apache.maven.artifact.repository.ArtifactRepository;
32  import org.apache.maven.artifact.resolver.ArtifactCollector;
33  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
34  import org.apache.maven.artifact.versioning.ArtifactVersion;
35  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
36  import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
37  import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
38  import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
39  import org.apache.maven.plugin.logging.Log;
40  import org.apache.maven.project.MavenProject;
41  import org.apache.maven.shared.dependency.tree.DependencyNode;
42  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
43  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
44  import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
45  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
46  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
47  import org.codehaus.plexus.i18n.I18N;
48  
49  /**
50   * Rule to enforce that the resolved dependency is also the most recent one of all transitive dependencies.
51   * 
52   * @author Geoffrey De Smet
53   * @since 1.1
54   */
55  public class RequireUpperBoundDeps
56      extends AbstractNonCacheableEnforcerRule
57  {
58      private static Log log;
59  
60      private static I18N i18n;
61  
62      /**
63       * @since 1.3
64       */
65      private boolean uniqueVersions;
66  
67      /**
68       * Set to {@code true} if timestamped snapshots should be used.
69       * 
70       * @param uniqueVersions 
71       * @since 1.3
72       */
73      public void setUniqueVersions( boolean uniqueVersions )
74      {
75          this.uniqueVersions = uniqueVersions;
76      }
77  
78      /**
79       * Uses the {@link EnforcerRuleHelper} to populate the values of the
80       * {@link DependencyTreeBuilder#buildDependencyTree(MavenProject, ArtifactRepository, ArtifactFactory, ArtifactMetadataSource, ArtifactFilter, ArtifactCollector)}
81       * factory method. <br/>
82       * This method simply exists to hide all the ugly lookup that the {@link EnforcerRuleHelper} has to do.
83       * 
84       * @param helper
85       * @return a Dependency Node which is the root of the project's dependency tree
86       * @throws EnforcerRuleException when the build should fail
87       */
88      private DependencyNode getNode( EnforcerRuleHelper helper )
89          throws EnforcerRuleException
90      {
91          try
92          {
93              MavenProject project = (MavenProject) helper.evaluate( "${project}" );
94              DependencyTreeBuilder dependencyTreeBuilder =
95                  (DependencyTreeBuilder) helper.getComponent( DependencyTreeBuilder.class );
96              ArtifactRepository repository = (ArtifactRepository) helper.evaluate( "${localRepository}" );
97              ArtifactFactory factory = (ArtifactFactory) helper.getComponent( ArtifactFactory.class );
98              ArtifactMetadataSource metadataSource =
99                  (ArtifactMetadataSource) helper.getComponent( ArtifactMetadataSource.class );
100             ArtifactCollector collector = (ArtifactCollector) helper.getComponent( ArtifactCollector.class );
101             ArtifactFilter filter = null; // we need to evaluate all scopes
102             DependencyNode node =
103                 dependencyTreeBuilder.buildDependencyTree( project, repository, factory, metadataSource, filter,
104                                                            collector );
105             return node;
106         }
107         catch ( ExpressionEvaluationException e )
108         {
109             throw new EnforcerRuleException( "Unable to lookup an expression " + e.getLocalizedMessage(), e );
110         }
111         catch ( ComponentLookupException e )
112         {
113             throw new EnforcerRuleException( "Unable to lookup a component " + e.getLocalizedMessage(), e );
114         }
115         catch ( DependencyTreeBuilderException e )
116         {
117             throw new EnforcerRuleException( "Could not build dependency tree " + e.getLocalizedMessage(), e );
118         }
119     }
120 
121     public void execute( EnforcerRuleHelper helper )
122         throws EnforcerRuleException
123     {
124         if ( log == null )
125         {
126             log = helper.getLog();
127         }
128         try
129         {
130             if ( i18n == null )
131             {
132                 i18n = (I18N) helper.getComponent( I18N.class );
133             }
134             DependencyNode node = getNode( helper );
135             RequireUpperBoundDepsVisitor visitor = new RequireUpperBoundDepsVisitor();
136             visitor.setUniqueVersions( uniqueVersions );
137             node.accept( visitor );
138             List<String> errorMessages = buildErrorMessages( visitor.getConflicts() );
139             if ( errorMessages.size() > 0 )
140             {
141                 throw new EnforcerRuleException( "Failed while enforcing RequireUpperBoundDeps. The error(s) are "
142                     + errorMessages );
143             }
144         }
145         catch ( ComponentLookupException e )
146         {
147             throw new EnforcerRuleException( "Unable to lookup a component " + e.getLocalizedMessage(), e );
148         }
149         catch ( Exception e )
150         {
151             throw new EnforcerRuleException( e.getLocalizedMessage(), e );
152         }
153     }
154 
155     private List<String> buildErrorMessages( List<List<DependencyNode>> conflicts )
156     {
157         List<String> errorMessages = new ArrayList<String>( conflicts.size() );
158         for ( List<DependencyNode> conflict : conflicts )
159         {
160             errorMessages.add( buildErrorMessage( conflict ) );
161         }
162         return errorMessages;
163     }
164 
165     private String buildErrorMessage( List<DependencyNode> conflict )
166     {
167         StringBuilder errorMessage = new StringBuilder();
168         errorMessage.append( "\nRequire upper bound dependencies error for "
169             + getFullArtifactName( conflict.get( 0 ), false ) + " paths to dependency are:\n" );
170         if ( conflict.size() > 0 )
171         {
172             errorMessage.append( buildTreeString( conflict.get( 0 ) ) );
173         }
174         for ( DependencyNode node : conflict.subList( 1, conflict.size() ) )
175         {
176             errorMessage.append( "and\n" );
177             errorMessage.append( buildTreeString( node ) );
178         }
179         return errorMessage.toString();
180     }
181 
182     private StringBuilder buildTreeString( DependencyNode node )
183     {
184         List<String> loc = new ArrayList<String>();
185         DependencyNode currentNode = node;
186         while ( currentNode != null )
187         {
188             StringBuilder line = new StringBuilder( getFullArtifactName( currentNode, false ) );
189             
190             if ( currentNode.getPremanagedVersion() != null )
191             {
192                 line.append( " (managed) <-- " );
193                 line.append( getFullArtifactName( currentNode, true ) );
194             }
195             
196             loc.add( line.toString() );
197             currentNode = currentNode.getParent();
198         }
199         Collections.reverse( loc );
200         StringBuilder builder = new StringBuilder();
201         for ( int i = 0; i < loc.size(); i++ )
202         {
203             for ( int j = 0; j < i; j++ )
204             {
205                 builder.append( "  " );
206             }
207             builder.append( "+-" ).append( loc.get( i ) );
208             builder.append( "\n" );
209         }
210         return builder;
211     }
212 
213     private String getFullArtifactName( DependencyNode node, boolean usePremanaged )
214     {
215         Artifact artifact = node.getArtifact();
216 
217         String version = node.getPremanagedVersion();
218         if ( !usePremanaged || version == null )
219         {
220             version = uniqueVersions ? artifact.getVersion() : artifact.getBaseVersion();
221         }
222         return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + version;
223     }
224 
225     private static class RequireUpperBoundDepsVisitor
226         implements DependencyNodeVisitor
227     {
228 
229         private boolean uniqueVersions;
230 
231         public void setUniqueVersions( boolean uniqueVersions )
232         {
233             this.uniqueVersions = uniqueVersions;
234         }
235 
236         private Map<String, List<DependencyNodeHopCountPair>> keyToPairsMap =
237             new LinkedHashMap<String, List<DependencyNodeHopCountPair>>();
238 
239         public boolean visit( DependencyNode node )
240         {
241             DependencyNodeHopCountPair pair = new DependencyNodeHopCountPair( node );
242             String key = pair.constructKey();
243             List<DependencyNodeHopCountPair> pairs = keyToPairsMap.get( key );
244             if ( pairs == null )
245             {
246                 pairs = new ArrayList<DependencyNodeHopCountPair>();
247                 keyToPairsMap.put( key, pairs );
248             }
249             pairs.add( pair );
250             Collections.sort( pairs );
251             return true;
252         }
253 
254         public boolean endVisit( DependencyNode node )
255         {
256             return true;
257         }
258 
259         public List<List<DependencyNode>> getConflicts()
260         {
261             List<List<DependencyNode>> output = new ArrayList<List<DependencyNode>>();
262             for ( List<DependencyNodeHopCountPair> pairs : keyToPairsMap.values() )
263             {
264                 if ( containsConflicts( pairs ) )
265                 {
266                     List<DependencyNode> outputSubList = new ArrayList<DependencyNode>( pairs.size() );
267                     for ( DependencyNodeHopCountPair pair : pairs )
268                     {
269                         outputSubList.add( pair.getNode() );
270                     }
271                     output.add( outputSubList );
272                 }
273             }
274             return output;
275         }
276 
277         @SuppressWarnings( "unchecked" )
278         private boolean containsConflicts( List<DependencyNodeHopCountPair> pairs )
279         {
280             DependencyNodeHopCountPair resolvedPair = pairs.get( 0 );
281 
282             // search for artifact with lowest hopCount
283             for ( DependencyNodeHopCountPair hopPair : pairs.subList( 1, pairs.size() ) )
284             {
285                 if ( hopPair.getHopCount() < resolvedPair.getHopCount() )
286                 {
287                     resolvedPair = hopPair;
288                 }
289             }
290 
291             ArtifactVersion resolvedVersion = resolvedPair.extractArtifactVersion( uniqueVersions, false );
292 
293             for ( DependencyNodeHopCountPair pair : pairs )
294             {
295                 ArtifactVersion version = pair.extractArtifactVersion( uniqueVersions, true );
296                 if ( resolvedVersion.compareTo( version ) < 0 )
297                 {
298                     return true;
299                 }
300             }
301             return false;
302         }
303 
304     }
305 
306     private static class DependencyNodeHopCountPair
307         implements Comparable<DependencyNodeHopCountPair>
308     {
309 
310         private DependencyNode node;
311 
312         private int hopCount;
313 
314         private DependencyNodeHopCountPair( DependencyNode node )
315         {
316             this.node = node;
317             countHops();
318         }
319 
320         private void countHops()
321         {
322             hopCount = 0;
323             DependencyNode parent = node.getParent();
324             while ( parent != null )
325             {
326                 hopCount++;
327                 parent = parent.getParent();
328             }
329         }
330 
331         private String constructKey()
332         {
333             Artifact artifact = node.getArtifact();
334             return artifact.getGroupId() + ":" + artifact.getArtifactId();
335         }
336 
337         public DependencyNode getNode()
338         {
339             return node;
340         }
341 
342         private ArtifactVersion extractArtifactVersion( boolean uniqueVersions, boolean usePremanagedVersion )
343         {
344             if ( usePremanagedVersion && node.getPremanagedVersion() != null )
345             {
346                 return new DefaultArtifactVersion( node.getPremanagedVersion() );
347             }
348 
349             Artifact artifact = node.getArtifact();
350             String version = uniqueVersions ? artifact.getVersion() : artifact.getBaseVersion();
351             if ( version != null )
352             {
353                 return new DefaultArtifactVersion( version );
354             }
355             try
356             {
357                 return artifact.getSelectedVersion();
358             }
359             catch ( OverConstrainedVersionException e )
360             {
361                 throw new RuntimeException( "Version ranges problem with " + node.getArtifact(), e );
362             }
363         }
364 
365         public int getHopCount()
366         {
367             return hopCount;
368         }
369 
370         public int compareTo( DependencyNodeHopCountPair other )
371         {
372             return Integer.valueOf( hopCount ).compareTo( Integer.valueOf( other.getHopCount() ) );
373         }
374     }
375 
376 }