/*
* Copyright 2011 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.googlecode.japi.checker.maven.plugin;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.artifact.InvalidDependencyVersionException;
import com.googlecode.japi.checker.BCChecker;
import com.googlecode.japi.checker.MuxReporter;
import com.googlecode.japi.checker.Rule;
import com.googlecode.japi.checker.SeverityCountReporter;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Goal which check the backward compatibility between generated
* artifact and a reference artifact.
*
* @goal check
* @phase verify
* @requiresDependencyResolution compile
* @threadSafe
*/
public class BackwardCompatibilityCheckerMojo
extends AbstractMojo
{
/**
* Location of the file.
* @parameter expression="${project.build.directory}"
* @required
*/
protected File outputDirectory;
/**
* @parameter default-value="${project.artifact}"
* @required
* @readonly
*/
private Artifact artifact;
/**
* @parameter
* @required
*/
private List<String> rules;
/**
* Which files should be included into the check.
* By default all class files are checked.
* @parameter
* @required
*/
private List<String> includes = new ArrayList<String>();
/**
* Which files should be excluded from the check.
* @parameter
* @required
*/
private List<String> excludes = new ArrayList<String>();
/**
* Reference version
* @parameter
* @required
*/
private ArtifactItem reference;
/**
* @parameter expression="${project}"
* @readonly
* @required
*/
private MavenProject project;
/**
* Used to look up Artifacts in the remote repository.
*
* @component role="org.apache.maven.artifact.factory.ArtifactFactory"
* @readonly
* @required
*/
private ArtifactFactory factory;
/**
* Used to look up Artifacts in the remote repository.
*
* @component
* @readonly
* @required
*/
private ArtifactResolver resolver;
/**
* ArtifactRepository of the localRepository. To obtain the directory of localRepository in unit tests use
* System.setProperty( "localRepository").
*
* @parameter expression="${localRepository}"
* @required
* @readonly
*/
private ArtifactRepository localRepository;
/**
* List of Remote Repositories used by the resolver
*
* @parameter expression="${project.remoteArtifactRepositories}"
* @readonly
* @required
*/
protected List<ArtifactRepository> remoteRepos;
/**
* The artifact collector to use.
*
* @component role="org.apache.maven.artifact.resolver.ArtifactCollector"
* @required
* @readonly
*/
protected ArtifactCollector artifactCollector;
/**
* The artifact metadata source to use.
*
* @component role="org.apache.maven.artifact.metadata.ArtifactMetadataSource" roleHint="maven"
* @readonly
*/
protected ArtifactMetadataSource artifactMetadataSource;
/**
*
* @component
*/
private MavenProjectBuilder projectBuilder;
/**
* {@inheritDoc}
*/
public void execute() throws MojoExecutionException, MojoFailureException {
if (artifact == null) {
throw new MojoExecutionException("Artifact is null.");
}
if (artifact.getFile() != null && artifact.getFile().exists()) {
// Retrieving the reference artifact.
updateArtifact(reference);
Artifact referenceArtifact = reference.getArtifact();
try {
// Creating a new checker which compare the generated artifact against the provided reference.
BCChecker checker = new BCChecker();
for (Artifact artifact : ((List<Artifact>)project.getCompileArtifacts())) {
this.getLog().debug("Adding new artifact dependency: " + artifact.getFile().toString());
checker.addToNewArtifactClasspath(artifact.getFile());
}
for (Artifact artifact : this.getDependencyList(reference.getGroupId(), reference.getArtifactId(), reference.getVersion())) {
this.getLog().debug("Adding reference dependency: " + artifact.getFile().toString());
checker.addToReferenceClasspath(artifact.getFile());
}
// configuring the reporting redirection
MuxReporter mux = new MuxReporter();
mux.add(new LogReporter(this.getLog()));
SeverityCountReporter ec = new SeverityCountReporter();
mux.add(ec);
// Configuring the check...
this.getLog().info("Checking backward compatibility of " + artifact.toString() + " against " + referenceArtifact.toString());
checker.setReporter(mux);
checker.setRules(getRuleInstances());
for (String include : getIncludes()) {
checker.addInclude(include);
}
for (String exclude : getExcludes()) {
checker.addExclude(exclude);
}
// Running the check...
checker.checkBacwardCompatibility(referenceArtifact.getFile(), artifact.getFile());
if (ec.hasSeverity()) {
getLog().error("You have " + ec.getCount() + " backward compatibility issues.");
throw new MojoFailureException("You have " + ec.getCount() + " backward compatibility issues.");
} else {
getLog().info("No backward compatibility issue found.");
}
} catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
} catch (IllegalArgumentException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
} else {
throw new MojoExecutionException("Could not find the artifact: " + artifact.toString());
}
}
protected List<Artifact> getDependencyList(String groupId, String artifactId, String version) throws MojoExecutionException {
try {
RuntimeDependencyResolver resolver = new RuntimeDependencyResolver(factory, this.resolver, artifactMetadataSource, localRepository, remoteRepos);
Set<Artifact> artifactSet = resolver.transitivelyResolvePomDependencies(projectBuilder, groupId, artifactId, version, true);
return new ArrayList<Artifact>(artifactSet);
} catch (MalformedURLException e) {
throw new MojoExecutionException("Cannot solve reference artifact: ", e);
} catch (ArtifactResolutionException e) {
throw new MojoExecutionException("Cannot solve reference artifact: ", e);
} catch (ArtifactNotFoundException e) {
throw new MojoExecutionException("Cannot solve reference artifact: ", e);
} catch (ProjectBuildingException e) {
throw new MojoExecutionException("Cannot solve reference artifact: ", e);
} catch (InvalidDependencyVersionException e) {
throw new MojoExecutionException("Cannot solve reference artifact: ", e);
}
}
private List<Rule> getRuleInstances() throws MojoExecutionException {
List<Rule> rules = new ArrayList<Rule>();
for (String classname : this.rules) {
try {
@SuppressWarnings("unchecked")
Class<Rule> clazz = (Class<Rule>)this.getClass().getClassLoader().loadClass(classname);
rules.add(clazz.newInstance());
} catch (ClassNotFoundException e) {
throw new MojoExecutionException(e.getMessage(), e);
} catch (InstantiationException e) {
throw new MojoExecutionException(e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}
return rules;
}
/**
* @return Returns the factory.
*/
public ArtifactFactory getFactory() {
return this.factory;
}
/**
* @param factory The factory to set.
*/
public void setFactory( ArtifactFactory factory ) {
this.factory = factory;
}
/**
* @return Returns the resolver.
*/
public ArtifactResolver getResolver() {
return this.resolver;
}
/**
* @param resolver The resolver to set.
*/
public void setResolver(ArtifactResolver resolver) {
this.resolver = resolver;
}
public ArtifactRepository getLocalRepository() {
return localRepository;
}
public void setLocalRepository(ArtifactRepository localRepository) {
this.localRepository = localRepository;
}
/**
* Resolves the Artifact from the remote repository if necessary. If no version is specified, it will be retrieved
* from the dependency list or from the DependencyManagement section of the pom.
*
* @param artifactItem containing information about artifact from plugin configuration.
* @return Artifact object representing the specified file.
* @throws MojoExecutionException with a message if the version can't be found in DependencyManagement.
*/
protected void updateArtifact(ArtifactItem artifactItem)
throws MojoExecutionException {
if (artifactItem.getArtifact() != null) {
return;
}
VersionRange vr;
try {
vr = VersionRange.createFromVersionSpec( artifactItem.getVersion() );
} catch ( InvalidVersionSpecificationException e ) {
vr = VersionRange.createFromVersion( artifactItem.getVersion() );
}
Artifact artifact = getFactory().createDependencyArtifact(artifactItem.getGroupId(), artifactItem.getArtifactId(), vr,
artifactItem.getType(), null, Artifact.SCOPE_COMPILE);
try {
getResolver().resolve(artifact, remoteRepos, localRepository);
} catch ( ArtifactResolutionException e ) {
throw new MojoExecutionException( "Unable to resolve artifact.", e );
} catch ( ArtifactNotFoundException e ) {
throw new MojoExecutionException( "Unable to find artifact.", e );
}
artifactItem.setArtifact(artifact);
}
public void setProject(MavenProject project) {
this.project = project;
}
public MavenProject getProject() {
return this.project;
}
public List<String> getIncludes() {
return includes;
}
public void setIncludes(List<String> includes) {
this.includes.addAll(includes);
}
public List<String> getExcludes() {
return excludes;
}
public void setExcludes(List<String> excludes) {
this.excludes.addAll(excludes);
}
}