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