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