1 package org.codehaus.mojo.animal_sniffer.maven;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 import org.apache.maven.artifact.Artifact;
29 import org.apache.maven.artifact.factory.ArtifactFactory;
30 import org.apache.maven.artifact.repository.ArtifactRepository;
31 import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
32 import org.apache.maven.artifact.resolver.ArtifactResolver;
33 import org.apache.maven.model.Dependency;
34 import org.apache.maven.plugin.AbstractMojo;
35 import org.apache.maven.plugin.MojoExecutionException;
36 import org.apache.maven.plugin.MojoFailureException;
37 import org.apache.maven.plugins.annotations.Component;
38 import org.apache.maven.plugins.annotations.LifecyclePhase;
39 import org.apache.maven.plugins.annotations.Mojo;
40 import org.apache.maven.plugins.annotations.Parameter;
41 import org.apache.maven.plugins.annotations.ResolutionScope;
42 import org.apache.maven.project.MavenProject;
43 import org.apache.maven.shared.artifact.filter.PatternExcludesArtifactFilter;
44 import org.apache.maven.shared.artifact.filter.PatternIncludesArtifactFilter;
45 import org.codehaus.mojo.animal_sniffer.ClassFileVisitor;
46 import org.codehaus.mojo.animal_sniffer.ClassListBuilder;
47 import org.codehaus.mojo.animal_sniffer.Clazz;
48 import org.codehaus.mojo.animal_sniffer.SignatureChecker;
49 import org.codehaus.plexus.util.StringUtils;
50
51 import java.io.File;
52 import java.io.FileInputStream;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.HashSet;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.Set;
61 import java.util.concurrent.ConcurrentHashMap;
62
63
64
65
66
67
68 @Mojo( name = "check", defaultPhase = LifecyclePhase.PROCESS_TEST_CLASSES, requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true )
69 public class CheckSignatureMojo
70 extends AbstractMojo
71 {
72
73
74
75
76 @Parameter( defaultValue = "${project.build.outputDirectory}", required = true, readonly = true )
77 protected File outputDirectory;
78
79
80
81
82
83
84 @Parameter( defaultValue = "${project.build.testOutputDirectory}", required = true, readonly = true )
85 protected File testOutputDirectory;
86
87
88
89
90
91
92 @Parameter( property = "animal.sniffer.checkTestClasses", defaultValue = "false" )
93 protected boolean checkTestClasses;
94
95
96
97
98 @Parameter( required = true, property = "animal.sniffer.signature" )
99 protected Signature signature;
100
101
102
103
104
105
106
107
108 public void setSignature( String signatureId ) {
109 String[] signatureParts = signatureId.split(":");
110 if(signatureParts.length == 3) {
111 this.signature = new Signature();
112 this.signature.setGroupId(signatureParts[0]);
113 this.signature.setArtifactId(signatureParts[1]);
114 this.signature.setVersion(signatureParts[2]);
115 }
116 }
117
118
119
120
121
122 @Parameter
123 protected String[] ignores;
124
125
126
127
128
129
130
131
132
133
134 @Parameter
135 protected String[] annotations;
136
137
138
139
140
141 @Parameter( defaultValue = "true" )
142 protected boolean ignoreDependencies;
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157 @Parameter
158 private String[] includeDependencies = null;
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173 @Parameter
174 private String[] excludeDependencies = null;
175
176
177
178
179
180 @Parameter( defaultValue = "false", property = "animal.sniffer.skip" )
181 protected boolean skip;
182
183
184
185
186
187 @Parameter( defaultValue = "true", property = "animal.sniffer.failOnError" )
188 protected boolean failOnError;
189
190
191
192 @Component
193 protected ArtifactResolver resolver;
194
195
196
197 @Parameter( defaultValue = "${project}", readonly = true )
198 protected MavenProject project;
199
200
201
202 @Parameter( defaultValue = "${localRepository}", readonly=true )
203 protected ArtifactRepository localRepository;
204
205
206
207 @Component
208 protected ArtifactFactory artifactFactory;
209
210 static Map<File, Map<String, Clazz>> classes = new ConcurrentHashMap<>();
211
212 @Override
213 public void execute()
214 throws MojoExecutionException, MojoFailureException
215 {
216 if ( skip )
217 {
218 getLog().info( "Signature checking is skipped." );
219 return;
220 }
221
222 if ( signature == null || StringUtils.isBlank(signature.getGroupId()) || signature.getGroupId() == "null") {
223 getLog().info( "Signature version is: " + signature.getVersion() );
224 return;
225 }
226
227 try
228 {
229 if ( StringUtils.isBlank( signature.getVersion() ) )
230 {
231 getLog().debug( "Resolving signature " + signature.getGroupId() + ":" + signature.getArtifactId()
232 + " version from dependencies" );
233 String source = "dependencies";
234 Dependency match = findMatchingDependency( signature, project.getDependencies() );
235 if ( match == null && project.getDependencyManagement() != null )
236 {
237 getLog().debug( "Resolving signature " + signature.getGroupId() + ":" + signature.getArtifactId()
238 + " version from dependencyManagement" );
239 source = "dependencyManagement";
240 match = findMatchingDependency( signature, project.getDependencyManagement().getDependencies() );
241 }
242 if ( match != null )
243 {
244 getLog().info( "Resolved signature " + signature.getGroupId() + ":" + signature.getArtifactId()
245 + " version as " + match.getVersion() + " from " + source);
246 signature.setVersion( match.getVersion() );
247 }
248 }
249
250 getLog().info( "Checking unresolved references to " + signature );
251
252 Artifact a = signature.createArtifact( artifactFactory );
253
254 resolver.resolve( a, project.getRemoteArtifactRepositories(), localRepository );
255
256 final Set<String> ignoredPackages = buildPackageList();
257
258 if ( ignores != null )
259 {
260 for ( String ignore : ignores )
261 {
262 if ( ignore == null )
263 {
264 continue;
265 }
266 ignoredPackages.add( ignore.replace( '.', '/' ) );
267 }
268 }
269
270 final SignatureChecker signatureChecker =
271 new SignatureChecker( loadClasses( a.getFile() ), ignoredPackages,
272 new MavenLogger( getLog() ) );
273 signatureChecker.setCheckJars( false );
274
275
276 signatureChecker.setSourcePath( buildSourcePathList() );
277
278 if ( annotations != null )
279 {
280 signatureChecker.setAnnotationTypes( Arrays.asList( annotations ) );
281 }
282
283 if ( checkTestClasses )
284 {
285 signatureChecker.process( new File[] { outputDirectory, testOutputDirectory } );
286 }
287 else
288 {
289 signatureChecker.process( outputDirectory );
290 }
291
292 if ( signatureChecker.isSignatureBroken() )
293 {
294 if (failOnError)
295 {
296 throw new MojoFailureException(
297 "Signature errors found. Verify them and ignore them with the proper annotation if needed." );
298 }
299 else
300 {
301 getLog().info(
302 "Signature errors found. Verify them and ignore them with the proper annotation if needed." );
303 }
304 } else {
305 getLog().debug( "No signature errors" );
306 }
307 }
308 catch ( IOException e )
309 {
310 throw new MojoExecutionException( "Failed to check signatures", e );
311 }
312 catch ( AbstractArtifactResolutionException e )
313 {
314 throw new MojoExecutionException( "Failed to obtain signature: " + signature, e );
315 }
316 }
317
318 private static Map<String, Clazz> loadClasses( File f ) throws IOException
319 {
320 Map<String, Clazz> classes = CheckSignatureMojo.classes.get( f );
321 if ( classes == null )
322 {
323 classes = SignatureChecker.loadClasses( new FileInputStream( f ) );
324 CheckSignatureMojo.classes.putIfAbsent( f, classes );
325 }
326 return classes;
327 }
328
329 private static Dependency findMatchingDependency( Signature signature, List<Dependency> dependencies )
330 {
331 Dependency match = null;
332 for ( Dependency d : dependencies )
333 {
334 if ( StringUtils.isBlank( d.getVersion() ) )
335 {
336 continue;
337 }
338 if ( StringUtils.equals( d.getGroupId(), signature.getGroupId() ) && StringUtils.equals( d.getArtifactId(),
339 signature.getArtifactId() ) )
340 {
341 if ( "signature".equals( d.getType() ) )
342 {
343
344 match = d;
345 break;
346 }
347 if ( "pom".equals( d.getType() ) )
348 {
349 if ( match == null || "jar".equals( match.getType() ) )
350 {
351 match = d;
352 }
353 }
354 if ( "jar".equals( d.getType() ) )
355 {
356 if ( match == null )
357 {
358 match = d;
359 }
360 }
361 }
362 }
363 return match;
364 }
365
366
367
368
369 private Set<String> buildPackageList()
370 throws IOException
371 {
372 ClassListBuilder plb = new ClassListBuilder( new MavenLogger( getLog() ) );
373 apply( plb );
374 return plb.getPackages();
375 }
376
377 private void apply( ClassFileVisitor v )
378 throws IOException
379 {
380 v.process( outputDirectory );
381 if ( checkTestClasses )
382 {
383 v.process( testOutputDirectory );
384 }
385 if ( ignoreDependencies )
386 {
387 PatternIncludesArtifactFilter includesFilter = includeDependencies == null
388 ? null
389 : new PatternIncludesArtifactFilter( Arrays.asList( includeDependencies ) );
390 PatternExcludesArtifactFilter excludesFilter = excludeDependencies == null
391 ? null
392 : new PatternExcludesArtifactFilter( Arrays.asList( excludeDependencies ) );
393
394 getLog().debug( "Building list of classes from dependencies" );
395
396 Set<String> classpathScopes = new HashSet<>(
397 Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_SYSTEM ) );
398 if ( checkTestClasses )
399 {
400 classpathScopes.addAll( Arrays.asList( Artifact.SCOPE_TEST, Artifact.SCOPE_RUNTIME ) );
401 }
402
403 for ( Artifact artifact : (Iterable<Artifact>) project.getArtifacts() )
404 {
405
406 if ( !artifact.getArtifactHandler().isAddedToClasspath() )
407 {
408 getLog().debug( "Skipping artifact " + BuildSignaturesMojo.artifactId( artifact )
409 + " as it is not added to the classpath." );
410 continue;
411 }
412
413 if ( !classpathScopes.contains( artifact.getScope() ) )
414 {
415 getLog().debug(
416 "Skipping artifact " + BuildSignaturesMojo.artifactId( artifact ) + " as it is not on the " + (
417 checkTestClasses
418 ? "test"
419 : "compile" ) + " classpath." );
420 continue;
421 }
422
423 if ( includesFilter != null && !includesFilter.include( artifact ) )
424 {
425 getLog().debug( "Skipping classes in artifact " + BuildSignaturesMojo.artifactId( artifact )
426 + " as it does not match include rules." );
427 continue;
428 }
429
430 if ( excludesFilter != null && !excludesFilter.include( artifact ) )
431 {
432 getLog().debug( "Skipping classes in artifact " + BuildSignaturesMojo.artifactId( artifact )
433 + " as it does matches exclude rules." );
434 continue;
435 }
436
437 getLog().debug(
438 "Adding classes in artifact " + BuildSignaturesMojo.artifactId( artifact ) + " to the ignores" );
439 v.process( artifact.getFile() );
440 }
441 }
442 }
443
444 @SuppressWarnings("unchecked")
445 private List<File> buildSourcePathList( )
446 {
447 List<String> compileSourceRoots = new ArrayList<>( project.getCompileSourceRoots() );
448 if ( checkTestClasses )
449 {
450 compileSourceRoots.addAll( project.getTestCompileSourceRoots() );
451 }
452 List<File> sourcePathList = new ArrayList<>( compileSourceRoots.size() );
453 for ( String compileSourceRoot : compileSourceRoots)
454 {
455 sourcePathList.add( new File( compileSourceRoot ) );
456 }
457 return sourcePathList;
458 }
459 }