package org.codehaus.mojo.animal_sniffer.enforcer; /* * The MIT License * * Copyright (c) 2008 Kohsuke Kawaguchi and codehaus.org. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.enforcer.rule.api.EnforcerRule; import org.apache.maven.enforcer.rule.api.EnforcerRuleException; import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper; import org.apache.maven.model.Dependency; import org.apache.maven.project.MavenProject; import org.apache.maven.shared.artifact.filter.PatternExcludesArtifactFilter; import org.apache.maven.shared.artifact.filter.PatternIncludesArtifactFilter; import org.codehaus.mojo.animal_sniffer.ClassFileVisitor; import org.codehaus.mojo.animal_sniffer.ClassListBuilder; import org.codehaus.mojo.animal_sniffer.SignatureChecker; import org.codehaus.mojo.animal_sniffer.logging.Logger; import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException; import org.codehaus.plexus.component.repository.exception.ComponentLookupException; import org.codehaus.plexus.util.StringUtils; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.Set; /** * Created by IntelliJ IDEA. * * @author connollys * @since Sep 4, 2009 2:44:29 PM */ public class CheckSignatureRule implements EnforcerRule { /** * Signature module to use. * * @required * @parameter */ protected Signature signature; /** * Class names to ignore signatures for (wildcards accepted). * * @parameter */ protected String[] ignores; /** * Annotation names to consider to ignore annotated methods, classes or fields. * <p> * By default {@value SignatureChecker#ANNOTATION_FQN} and * {@value SignatureChecker#PREVIOUS_ANNOTATION_FQN} are used. * * @parameter * @see SignatureChecker#ANNOTATION_FQN * @see SignatureChecker#PREVIOUS_ANNOTATION_FQN */ protected String[] annotations; /** * Should dependencies be ignored. * * @parameter default-value="true" */ protected boolean ignoreDependencies = true; /** * A list of artifact patterns to include. Patterns can include <code>*</code> as a wildcard match for any * <b>whole</b> segment, valid patterns are: * <ul> * <li><code>groupId:artifactId</code></li> * <li><code>groupId:artifactId:type</code></li> * <li><code>groupId:artifactId:type:version</code></li> * <li><code>groupId:artifactId:type:classifier</code></li> * <li><code>groupId:artifactId:type:classifier:version</code></li> * </ul> * * @parameter * @since 1.12 */ private String[] includeDependencies = null; /** * A list of artifact patterns to exclude. Patterns can include <code>*</code> as a wildcard match for any * <b>whole</b> segment, valid patterns are: * <ul> * <li><code>groupId:artifactId</code></li> * <li><code>groupId:artifactId:type</code></li> * <li><code>groupId:artifactId:type:version</code></li> * <li><code>groupId:artifactId:type:classifier</code></li> * <li><code>groupId:artifactId:type:classifier:version</code></li> * </ul> * * @parameter * @since 1.12 */ private String[] excludeDependencies = null; public void execute( EnforcerRuleHelper helper ) throws EnforcerRuleException { try { File outputDirectory = new File( (String) helper.evaluate( "${project.build.outputDirectory}" ) ); ArtifactResolver resolver = (ArtifactResolver) helper.getComponent( ArtifactResolver.class ); MavenProject project = (MavenProject) helper.evaluate( "${project}" ); ArtifactRepository localRepository = (ArtifactRepository) helper.evaluate( "${localRepository}" ); ArtifactFactory artifactFactory = (ArtifactFactory) helper.getComponent( ArtifactFactory.class ); if ( StringUtils.isEmpty( signature.getVersion() ) ) { helper.getLog().debug( "Resolving signature " + signature.getGroupId() + ":" + signature.getArtifactId() + " version from dependencies" ); String source = "dependencies"; Dependency match = findMatchingDependency( signature, project.getDependencies() ); if ( match == null ) { helper.getLog().debug( "Resolving signature " + signature.getGroupId() + ":" + signature.getArtifactId() + " version from dependencyManagement" ); source = "dependencyManagement"; match = findMatchingDependency( signature, project.getDependencyManagement().getDependencies() ); } if ( match != null ) { helper.getLog().info( "Resolved signature " + signature.getGroupId() + ":" + signature.getArtifactId() + " version as " + match.getVersion() + " from " + source); signature.setVersion( match.getVersion() ); } } helper.getLog().info( "Checking unresolved references to " + signature ); org.apache.maven.artifact.Artifact a = signature.createArtifact( artifactFactory ); resolver.resolve( a, project.getRemoteArtifactRepositories(), localRepository ); // just check code from this module MavenLogger logger = new MavenLogger( helper.getLog() ); final Set<String> ignoredPackages = buildPackageList( outputDirectory, project, logger ); if ( ignores != null ) { for ( int i = 0; i < ignores.length; i++ ) { String ignore = ignores[i]; if ( ignore == null ) { continue; } ignoredPackages.add( ignore.replace( '.', '/' ) ); } } final SignatureChecker signatureChecker = new SignatureChecker( new FileInputStream( a.getFile() ), ignoredPackages, logger ); signatureChecker.setCheckJars( false ); // don't want to descend into jar files that have been copied to // the output directory as resources. List<File> sourcePaths = new ArrayList<File>(); Iterator iterator = project.getCompileSourceRoots().iterator(); while ( iterator.hasNext() ) { String path = (String) iterator.next(); sourcePaths.add( new File( path ) ); } signatureChecker.setSourcePath( sourcePaths ); if ( annotations != null ) { signatureChecker.setAnnotationTypes( Arrays.asList( annotations ) ); } signatureChecker.process( outputDirectory ); if ( signatureChecker.isSignatureBroken() ) { throw new EnforcerRuleException( "Signature errors found. Verify them and ignore them with the proper annotation if needed." ); } } catch ( IOException e ) { throw new EnforcerRuleException( "Failed to check signatures", e ); } catch ( AbstractArtifactResolutionException e ) { throw new EnforcerRuleException( "Failed to obtain signature: " + signature, e ); } catch ( ComponentLookupException e ) { throw new EnforcerRuleException( "Unable to lookup a component " + e.getLocalizedMessage(), e ); } catch ( ExpressionEvaluationException e ) { throw new EnforcerRuleException( "Unable to lookup an expression " + e.getLocalizedMessage(), e ); } } private static Dependency findMatchingDependency( Signature signature, List<Dependency> dependencies ) { Dependency match = null; for ( Dependency d : dependencies ) { if ( StringUtils.isEmpty( d.getVersion() ) ) { continue; } if ( StringUtils.equals( d.getGroupId(), signature.getGroupId() ) && StringUtils.equals( d.getArtifactId(), signature.getArtifactId() ) ) { if ( "signature".equals( d.getType() ) ) { // this is a perfect match match = d; break; } if ( "pom".equals( d.getType() ) ) { if ( match == null || "jar".equals( match.getType() ) ) { match = d; } } if ( "jar".equals( d.getType() ) ) { if ( match == null ) { match = d; } } } } return match; } /** * List of packages defined in the application. * * @param outputDirectory * @param logger */ private Set<String> buildPackageList( File outputDirectory, MavenProject project, Logger logger ) throws IOException { ClassListBuilder plb = new ClassListBuilder( logger ); apply( plb, outputDirectory, project, logger ); return plb.getPackages(); } private void apply( ClassFileVisitor v, File outputDirectory, MavenProject project, Logger logger ) throws IOException { v.process( outputDirectory ); if ( ignoreDependencies ) { PatternIncludesArtifactFilter includesFilter = includeDependencies == null ? null : new PatternIncludesArtifactFilter( Arrays.asList( includeDependencies ) ); PatternExcludesArtifactFilter excludesFilter = excludeDependencies == null ? null : new PatternExcludesArtifactFilter( Arrays.asList( excludeDependencies ) ); logger.debug( "Building list of classes from dependencies" ); for ( Iterator i = project.getArtifacts().iterator(); i.hasNext(); ) { Artifact artifact = (Artifact) i.next(); if ( !artifact.getArtifactHandler().isAddedToClasspath() ) { logger.debug( "Skipping artifact " + artifactId( artifact ) + " as it is not added to the classpath." ); continue; } if ( !( Artifact.SCOPE_COMPILE.equals( artifact.getScope() ) || Artifact.SCOPE_PROVIDED.equals( artifact.getScope() ) || Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) ) ) { logger.debug( "Skipping artifact " + artifactId( artifact ) + " as it is not on the compile classpath." ); continue; } if ( includesFilter != null && !includesFilter.include( artifact ) ) { logger.debug( "Skipping classes in artifact " + artifactId( artifact ) + " as it does not match include rules." ); continue; } if ( excludesFilter != null && !excludesFilter.include( artifact ) ) { logger.debug( "Skipping classes in artifact " + artifactId( artifact ) + " as it does matches exclude rules." ); continue; } logger.debug( "Adding classes in artifact " + artifactId( artifact ) + " to the ignores" ); v.process( artifact.getFile() ); } } } public boolean isCacheable() { return false; } public boolean isResultValid( EnforcerRule enforcerRule ) { return false; } public String getCacheId() { return getClass().getName() + new Random().nextLong(); } private static String artifactId( Artifact artifact ) { return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType() + ( artifact.getClassifier() != null ? ":" + artifact.getClassifier() : "" ) + ":" + artifact.getBaseVersion(); } }