1 package org.apache.maven.plugins.enforcer;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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.execution.MavenSession;
40 import org.apache.maven.plugin.logging.Log;
41 import org.apache.maven.project.DefaultProjectBuildingRequest;
42 import org.apache.maven.project.MavenProject;
43 import org.apache.maven.project.ProjectBuildingRequest;
44 import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
45 import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
46 import org.apache.maven.shared.dependency.graph.DependencyNode;
47 import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
48 import org.apache.maven.shared.utils.logging.MessageUtils;
49 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
50 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
51
52
53
54
55
56
57
58 public class RequireUpperBoundDeps
59 extends AbstractNonCacheableEnforcerRule
60 {
61 private static Log log;
62
63
64
65
66 private boolean uniqueVersions;
67
68
69
70
71
72
73 private List<String> excludes = null;
74
75
76
77
78
79
80 private List<String> includes = null;
81
82
83
84
85
86
87
88 public void setUniqueVersions( boolean uniqueVersions )
89 {
90 this.uniqueVersions = uniqueVersions;
91 }
92
93
94
95
96
97 public void setExcludes( List<String> excludes )
98 {
99 this.excludes = excludes;
100 }
101
102
103
104
105
106
107 public void setIncludes( List<String> includes )
108 {
109 this.includes = includes;
110 }
111
112
113
114
115
116
117
118
119
120
121
122
123
124 private DependencyNode getNode( EnforcerRuleHelper helper )
125 throws EnforcerRuleException
126 {
127 try
128 {
129 MavenProject project = (MavenProject) helper.evaluate( "${project}" );
130 MavenSession session = (MavenSession) helper.evaluate( "${session}" );
131 DependencyCollectorBuilder dependencyCollectorBuilder =
132 helper.getComponent( DependencyCollectorBuilder.class );
133 ArtifactRepository repository = (ArtifactRepository) helper.evaluate( "${localRepository}" );
134
135 ProjectBuildingRequest buildingRequest =
136 new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
137 buildingRequest.setProject( project );
138 buildingRequest.setLocalRepository( repository );
139 ArtifactFilter filter = null;
140 return dependencyCollectorBuilder.collectDependencyGraph( buildingRequest, filter );
141 }
142 catch ( ExpressionEvaluationException e )
143 {
144 throw new EnforcerRuleException( "Unable to lookup an expression " + e.getLocalizedMessage(), e );
145 }
146 catch ( ComponentLookupException e )
147 {
148 throw new EnforcerRuleException( "Unable to lookup a component " + e.getLocalizedMessage(), e );
149 }
150 catch ( DependencyCollectorBuilderException e )
151 {
152 throw new EnforcerRuleException( "Could not build dependency tree " + e.getLocalizedMessage(), e );
153 }
154 }
155
156 @Override
157 public void execute( EnforcerRuleHelper helper )
158 throws EnforcerRuleException
159 {
160 if ( log == null )
161 {
162 log = helper.getLog();
163 }
164 DependencyNode node = getNode( helper );
165 RequireUpperBoundDepsVisitor visitor = new RequireUpperBoundDepsVisitor();
166 visitor.setUniqueVersions( uniqueVersions );
167 visitor.setIncludes( includes );
168 node.accept( visitor );
169 List<String> errorMessages = buildErrorMessages( visitor.getConflicts() );
170 if ( errorMessages.size() > 0 )
171 {
172 throw new EnforcerRuleException( "Failed while enforcing RequireUpperBoundDeps. The error(s) are "
173 + errorMessages );
174 }
175 }
176
177 private List<String> buildErrorMessages( List<List<DependencyNode>> conflicts )
178 {
179 List<String> errorMessages = new ArrayList<>( conflicts.size() );
180 for ( List<DependencyNode> conflict : conflicts )
181 {
182 Artifact artifact = conflict.get( 0 ).getArtifact();
183 String groupArt = artifact.getGroupId() + ":" + artifact.getArtifactId();
184 if ( excludes != null && excludes.contains( groupArt ) )
185 {
186 log.info( "Ignoring requireUpperBoundDeps in " + groupArt );
187 }
188 else
189 {
190 errorMessages.add( buildErrorMessage( conflict ) );
191 }
192 }
193 return errorMessages;
194 }
195
196 private String buildErrorMessage( List<DependencyNode> conflict )
197 {
198 StringBuilder errorMessage = new StringBuilder();
199 errorMessage.append(
200 System.lineSeparator() + "Require upper bound dependencies error for " + getFullArtifactName(
201 conflict.get( 0 ), false ) + " paths to dependency are:" + System.lineSeparator() );
202 if ( conflict.size() > 0 )
203 {
204 errorMessage.append( buildTreeString( conflict.get( 0 ) ) );
205 }
206 for ( DependencyNode node : conflict.subList( 1, conflict.size() ) )
207 {
208 errorMessage.append( "and" + System.lineSeparator() );
209 errorMessage.append( buildTreeString( node ) );
210 }
211 return errorMessage.toString();
212 }
213
214 private StringBuilder buildTreeString( DependencyNode node )
215 {
216 List<String> loc = new ArrayList<>();
217 DependencyNode currentNode = node;
218 while ( currentNode != null )
219 {
220 StringBuilder line = new StringBuilder( getFullArtifactName( currentNode, false ) );
221
222 if ( currentNode.getPremanagedVersion() != null )
223 {
224 line.append( " (managed) <-- " );
225 line.append( getFullArtifactName( currentNode, true ) );
226 }
227
228 loc.add( line.toString() );
229 currentNode = currentNode.getParent();
230 }
231 Collections.reverse( loc );
232 StringBuilder builder = new StringBuilder();
233 for ( int i = 0; i < loc.size(); i++ )
234 {
235 for ( int j = 0; j < i; j++ )
236 {
237 builder.append( " " );
238 }
239 builder.append( "+-" ).append( loc.get( i ) );
240 builder.append( System.lineSeparator() );
241 }
242 return builder;
243 }
244
245 private String getFullArtifactName( DependencyNode node, boolean usePremanaged )
246 {
247 Artifact artifact = node.getArtifact();
248
249 String version = node.getPremanagedVersion();
250 if ( !usePremanaged || version == null )
251 {
252 version = uniqueVersions ? artifact.getVersion() : artifact.getBaseVersion();
253 }
254 String result = artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + version;
255
256 String classifier = artifact.getClassifier();
257 if ( classifier != null && !classifier.isEmpty() )
258 {
259 result += ":" + classifier;
260 }
261
262 String scope = artifact.getScope();
263 if ( "compile".equals( scope ) )
264 {
265 result = MessageUtils.buffer().strong( result ).toString();
266 }
267 else if ( scope != null )
268 {
269 result += " [" + scope + ']';
270 }
271
272 return result;
273 }
274
275 private static class RequireUpperBoundDepsVisitor
276 implements DependencyNodeVisitor
277 {
278
279 private boolean uniqueVersions;
280
281 private List<String> includes = null;
282
283 public void setUniqueVersions( boolean uniqueVersions )
284 {
285 this.uniqueVersions = uniqueVersions;
286 }
287
288 public void setIncludes( List<String> includes )
289 {
290 this.includes = includes;
291 }
292
293 private Map<String, List<DependencyNodeHopCountPair>> keyToPairsMap = new LinkedHashMap<>();
294
295 public boolean visit( DependencyNode node )
296 {
297 DependencyNodeHopCountPair pair = new DependencyNodeHopCountPair( node );
298 String key = pair.constructKey();
299
300 if ( includes != null && !includes.isEmpty() && !includes.contains( key ) )
301 {
302 return true;
303 }
304
305 List<DependencyNodeHopCountPair> pairs = keyToPairsMap.get( key );
306 if ( pairs == null )
307 {
308 pairs = new ArrayList<>();
309 keyToPairsMap.put( key, pairs );
310 }
311 pairs.add( pair );
312 Collections.sort( pairs );
313 return true;
314 }
315
316 public boolean endVisit( DependencyNode node )
317 {
318 return true;
319 }
320
321 public List<List<DependencyNode>> getConflicts()
322 {
323 List<List<DependencyNode>> output = new ArrayList<>();
324 for ( List<DependencyNodeHopCountPair> pairs : keyToPairsMap.values() )
325 {
326 if ( containsConflicts( pairs ) )
327 {
328 List<DependencyNode> outputSubList = new ArrayList<>( pairs.size() );
329 for ( DependencyNodeHopCountPair pair : pairs )
330 {
331 outputSubList.add( pair.getNode() );
332 }
333 output.add( outputSubList );
334 }
335 }
336 return output;
337 }
338
339 private boolean containsConflicts( List<DependencyNodeHopCountPair> pairs )
340 {
341 DependencyNodeHopCountPair resolvedPair = pairs.get( 0 );
342
343
344 for ( DependencyNodeHopCountPair hopPair : pairs.subList( 1, pairs.size() ) )
345 {
346 if ( hopPair.getHopCount() < resolvedPair.getHopCount() )
347 {
348 resolvedPair = hopPair;
349 }
350 }
351
352 ArtifactVersion resolvedVersion = resolvedPair.extractArtifactVersion( uniqueVersions, false );
353
354 for ( DependencyNodeHopCountPair pair : pairs )
355 {
356 ArtifactVersion version = pair.extractArtifactVersion( uniqueVersions, true );
357 if ( resolvedVersion.compareTo( version ) < 0 )
358 {
359 return true;
360 }
361 }
362 return false;
363 }
364
365 }
366
367 private static class DependencyNodeHopCountPair
368 implements Comparable<DependencyNodeHopCountPair>
369 {
370
371 private DependencyNode node;
372
373 private int hopCount;
374
375 private DependencyNodeHopCountPair( DependencyNode node )
376 {
377 this.node = node;
378 countHops();
379 }
380
381 private void countHops()
382 {
383 hopCount = 0;
384 DependencyNode parent = node.getParent();
385 while ( parent != null )
386 {
387 hopCount++;
388 parent = parent.getParent();
389 }
390 }
391
392 private String constructKey()
393 {
394 Artifact artifact = node.getArtifact();
395 return artifact.getGroupId() + ":" + artifact.getArtifactId();
396 }
397
398 public DependencyNode getNode()
399 {
400 return node;
401 }
402
403 private ArtifactVersion extractArtifactVersion( boolean uniqueVersions, boolean usePremanagedVersion )
404 {
405 if ( usePremanagedVersion && node.getPremanagedVersion() != null )
406 {
407 return new DefaultArtifactVersion( node.getPremanagedVersion() );
408 }
409
410 Artifact artifact = node.getArtifact();
411 String version = uniqueVersions ? artifact.getVersion() : artifact.getBaseVersion();
412 if ( version != null )
413 {
414 return new DefaultArtifactVersion( version );
415 }
416 try
417 {
418 return artifact.getSelectedVersion();
419 }
420 catch ( OverConstrainedVersionException e )
421 {
422 throw new RuntimeException( "Version ranges problem with " + node.getArtifact(), e );
423 }
424 }
425
426 public int getHopCount()
427 {
428 return hopCount;
429 }
430
431 public int compareTo( DependencyNodeHopCountPair other )
432 {
433 return Integer.valueOf( hopCount ).compareTo( Integer.valueOf( other.getHopCount() ) );
434 }
435 }
436
437 }