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