com.ning.maven.plugins.dependencyversionscheck.AbstractDependencyVersionsMojo.java Source code

Java tutorial

Introduction

Here is the source code for com.ning.maven.plugins.dependencyversionscheck.AbstractDependencyVersionsMojo.java

Source

/*
 * Copyright 2010 Ning, Inc.
 *
 * Ning licenses this file to you 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.ning.maven.plugins.dependencyversionscheck;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.regex.Pattern;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
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.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactCollector;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.artifact.resolver.MultipleArtifactsNotFoundException;
import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Exclusion;
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 org.apache.maven.project.artifact.MavenMetadataSource;
import org.apache.maven.shared.dependency.tree.DependencyNode;
import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Throwables;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.Striped;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.ning.maven.plugins.dependencyversionscheck.strategy.Strategy;
import com.ning.maven.plugins.dependencyversionscheck.strategy.StrategyProvider;
import com.ning.maven.plugins.dependencyversionscheck.util.ArtifactOptionalFilter;
import com.ning.maven.plugins.dependencyversionscheck.util.ArtifactScopeFilter;
import com.ning.maven.plugins.dependencyversionscheck.version.Version;
import com.ning.maven.plugins.dependencyversionscheck.version.VersionResolution;
import com.pyx4j.log4j.MavenLogAppender;

/**
 * Base code for all the mojos. Contains the dependency resolvers and the common options.
 */
public abstract class AbstractDependencyVersionsMojo extends AbstractMojo {
    private static final int DEPENDENCY_RESOLUTION_NUM_THREADS = Runtime.getRuntime().availableProcessors() * 5;

    /**
     * The maven project (effective pom).
     *
     * @parameter expression="${project}"
     * @required
     * @readonly
     */
    protected MavenProject project;

    /**
     * For creating MavenProject objects.
     *
     * @component
     */
    protected MavenProjectBuilder mavenProjectBuilder;

    /**
     * The artifact factory.
     *
     * @component
     */
    protected ArtifactFactory artifactFactory;

    /**
     * Resolves artifacts.
     *
     * @component
     */
    protected ArtifactResolver artifactResolver;

    /**
     * The strategy provider. This can be requested by other pieces to add
     * additional strategies.
     *
     * @component
     */
    protected StrategyProvider strategyProvider;

    /**
     * For resolving versions.
     *
     * @component
     */
    protected ArtifactMetadataSource artifactMetadataSource;

    /**
     * The local repo for the project if defined;
     *
     * @parameter expression="${localRepository}"
     * @required
     * @readonly
     */
    protected ArtifactRepository localRepository;

    /**
     * @component
     * @required
     * @readonly
     */
    protected DependencyTreeBuilder treeBuilder;

    /**
     * @component
     * @required
     * @readonly
     */
    protected ArtifactCollector artifactCollector;

    /**
     * Remote repositories.
     *
     * @parameter expression="${project.remoteArtifactRepositories}"
     * @required
     * @readonly
     */
    protected List remoteRepositories;

    /**
     * A set of artifacts with expected and resolved versions that are to be except from the check.
     *
     * @parameter alias="exceptions"
     */
    protected VersionCheckExcludes[] exceptions;

    /**
     * Skip the plugin execution.
     *
     * <pre>
     *   <configuration>
     *     <skip>true</skip>
     *   </configuration>
     * </pre>
     *
     * @parameter default-value="false"
     */
    protected boolean skip = false;

    /**
     * Whether to warn if the resolved major version is higher then the expected one of the project or one of the dependencies.
     *
     * @parameter default-value="false"
     */
    protected boolean warnIfMajorVersionIsHigher;

    /**
     * Whether to run dependency resolution in parallel.
     *
     * @parameter expression="useParallelDependencyResolution"
     *            default-value="true"
     *
     */
    protected boolean useParallelDependencyResolution;

    /**
     * Resolvers to resolve versions and compare existing things.
     *
     * <pre>
     *   <resolvers>
     *     <resolver>
     *       <id>apache-stuff</id>
     *       <strategy>APR</strategy>
     *       <includes>
     *         <include>commons-configuration:commons-configuration</include>
     *         <include>commons-lang:commons-lang</include>
     *       </includes>
     *     </resolver>
     *   </resolver>
     * </pre>
     *
     * @parameter alias="resolvers"
     */
    protected ResolverDefinition[] resolvers;

    /**
     * Sets the default strategy.
     *
     * @parameter alias="defaultStrategy" default-value="default"
     *
     */
    protected String defaultStrategy = "default";

    /** Lists all available scopes for transitive dependency resolution. */
    protected static final Map TRANSITIVE_SCOPES;

    /** Lists all visible scopes when doing dependency resolution. */
    protected static final Map VISIBLE_SCOPES;

    static {
        final Map transitiveScopes = new HashMap();
        // Map from the scope to test to the scopes that show up in this scope from a transitive dep. null value is for "all available scopes".
        transitiveScopes.put(Artifact.SCOPE_COMPILE,
                new String[] { Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM });
        transitiveScopes.put(Artifact.SCOPE_TEST,
                new String[] { Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME });
        transitiveScopes.put(Artifact.SCOPE_RUNTIME,
                new String[] { Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME });
        transitiveScopes.put(null,
                new String[] { Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME });
        TRANSITIVE_SCOPES = Collections.unmodifiableMap(transitiveScopes);

        // Map from the scope to the scopes that are visible on the classpath for that scope. null value is for "all available scopes".
        final Map visibleScopes = new HashMap();
        visibleScopes.put(Artifact.SCOPE_COMPILE,
                new String[] { Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_SYSTEM });
        visibleScopes.put(Artifact.SCOPE_TEST, new String[] { Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED,
                Artifact.SCOPE_SYSTEM, Artifact.SCOPE_TEST });
        visibleScopes.put(Artifact.SCOPE_RUNTIME,
                new String[] { Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME });
        visibleScopes.put(null, new String[] { Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED,
                Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME, Artifact.SCOPE_TEST });
        VISIBLE_SCOPES = Collections.unmodifiableMap(visibleScopes);
    }

    protected final Logger LOG = LoggerFactory.getLogger(this.getClass());

    /** ArtifactName to VersionStrategy. Filled in loadResolvers(). */
    protected final Map resolverMap = new HashMap();

    /** Artifact pattern to VersionStrategy. Filled in loadResolvers(). */
    protected final Map resolverPatternMap = new HashMap();

    /** Qualified artifact name to artifact. */
    protected final Map resolvedDependenciesByName = new HashMap();

    protected Strategy defaultStrategyType;

    /** Keeps track of the longest name for an artifact for printing out nicely. */
    protected int maxLen = -1;

    private final ListeningExecutorService executorService = MoreExecutors.listeningDecorator(
            Executors.newFixedThreadPool(DEPENDENCY_RESOLUTION_NUM_THREADS, new ThreadFactoryBuilder()
                    .setNameFormat("dependency-version-check-worker-%s").setDaemon(true).build()));

    private final Striped resolutionMapLocks = Striped.lock(DEPENDENCY_RESOLUTION_NUM_THREADS);

    public void execute() throws MojoExecutionException, MojoFailureException {
        MavenLogAppender.startPluginLog(this);

        try {
            if (skip) {
                LOG.debug("Skipping execution!");
            } else {
                checkExceptions();

                final DependencyNode node = treeBuilder.buildDependencyTree(project, localRepository,
                        artifactFactory, artifactMetadataSource, null, artifactCollector);

                for (final Iterator dependencyIt = node.iterator(); dependencyIt.hasNext();) {
                    final DependencyNode dependency = (DependencyNode) dependencyIt.next();
                    if (dependency.getState() == DependencyNode.INCLUDED) {
                        final Artifact artifact = dependency.getArtifact();
                        resolvedDependenciesByName.put(getQualifiedName(artifact), artifact);
                    }
                }

                loadResolvers(resolvers);

                defaultStrategyType = strategyProvider.forName(defaultStrategy);
                if (defaultStrategyType == null) {
                    throw new MojoExecutionException(
                            "Could not locate default strategy '" + defaultStrategy + "'!");
                }

                LOG.debug("Starting {} mojo run!", this.getClass().getSimpleName());
                doExecute();
            }
        } catch (MojoExecutionException me) {
            throw me;
        } catch (MojoFailureException mfe) {
            throw mfe;
        } catch (Exception e) {
            throw new MojoExecutionException("While running mojo: ", e);
        } finally {
            executorService.shutdownNow();
            LOG.debug("Ended {} mojo run!", this.getClass().getSimpleName());
            MavenLogAppender.endPluginLog(this);
        }
    }

    /**
     * Subclasses need to implement this method.
     */
    protected abstract void doExecute() throws Exception;

    /**
     * Loads all resolver definitions and turns them into either direct resolved strategies or patterns to check against.
     */
    private void loadResolvers(final ResolverDefinition[] resolvers) {
        if (!ArrayUtils.isEmpty(resolvers)) {
            for (int i = 0; i < resolvers.length; i++) {
                ResolverDefinition r = resolvers[i];

                final Strategy strategy = strategyProvider.forName(r.getStrategyName());
                if (strategy == null) {
                    LOG.warn("Could not locate Strategy {}! Check for typos!", r.getStrategyName());
                } else {
                    final String[] includes = r.getIncludes();
                    if (!ArrayUtils.isEmpty(includes)) {
                        for (int j = 0; j < includes.length; j++) {
                            final Strategy oldStrategy = (Strategy) resolverMap.get(includes[j]);
                            if (oldStrategy != null) {
                                LOG.warn("A strategy for {} was already defined: {}", includes[j],
                                        oldStrategy.getName());
                            }
                            if (includes[j].contains("*")) {
                                // Poor mans regexp escape. Escapes all "." and turns "*" into ".*". Should be good enough
                                // for most use cases. Pattern.quote() only adds \\Q and \\E to the string and does not metachar
                                // escaping, so it is useless.
                                final String pattern = includes[j].replace(".", "\\.").replace("*", ".*");
                                resolverPatternMap.put(pattern, strategy);
                            } else {
                                resolverMap.put(includes[j], strategy);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Locate the version strategy for a given version resolution. This will try
     * to locate a direct match and also do wildcard match on "group only" and "group and artifact".
     */
    private Strategy findStrategy(final VersionResolution resolution) {
        final String dependencyName = resolution.getDependencyName();
        Strategy strategy = (Strategy) resolverMap.get(dependencyName);
        if (strategy != null) {
            LOG.debug("Found direct match: {}", strategy.getName());
            return strategy;
        }

        // No direct hit. Try just the group
        final String[] elements = StringUtils.split(dependencyName, ":");

        if (elements.length == 2) {
            strategy = (Strategy) resolverMap.get(elements[0]);

            if (strategy != null) {
                LOG.debug("Found group ({}) match: {}", elements[0], strategy.getName());

                resolverMap.put(dependencyName, strategy);
                return strategy;
            }

            // Try the wildcards
            for (Iterator it = resolverPatternMap.entrySet().iterator(); it.hasNext();) {
                final Map.Entry entry = (Map.Entry) it.next();
                final String pattern = (String) entry.getKey();
                final String patternElements[] = StringUtils.split(pattern, ":");

                if (Pattern.matches(patternElements[0], elements[0])) {
                    // group wildcard match.
                    if (patternElements.length == 1) {
                        strategy = (Strategy) entry.getValue();
                        LOG.debug("Found pattern match ({}) on group ({}) match: {}",
                                new Object[] { patternElements[0], elements[0], strategy.getName() });

                        resolverMap.put(dependencyName, strategy);
                        return strategy;
                    }
                    // group and artifact wildcard match.
                    else if (Pattern.matches(patternElements[1], elements[1])) {
                        strategy = (Strategy) entry.getValue();
                        LOG.debug("Found regexp match ({}) on ({}) match: {}",
                                new Object[] { pattern, dependencyName, strategy.getName() });

                        resolverMap.put(dependencyName, strategy);
                        return strategy;
                    }
                }
            }
        }

        strategy = defaultStrategyType;
        resolverMap.put(dependencyName, strategy);
        LOG.debug("Using default strategy for {} match: {}", dependencyName, strategy.getName());
        return strategy;
    }

    /**
     * Creates a map of all version resolutions used in this project in a given scope. The result is a map from artifactName to a list of version numbers used in the project, based on the element
     * requesting
     * the version.
     *
     * If the special scope "null" is used, a superset of all scopes is used (this is used by the check mojo).
     */
    protected Map buildResolutionMap(final String scope)
            throws MojoExecutionException, InvalidDependencyVersionException, ProjectBuildingException,
            ArtifactResolutionException, ArtifactNotFoundException {
        final String[] visibleScopes = (String[]) VISIBLE_SCOPES.get(scope);
        final String[] transitiveScopes = (String[]) TRANSITIVE_SCOPES.get(scope);

        if (visibleScopes == null) {
            throw new MojoExecutionException("No valid scopes found for '" + scope + "'");
        }

        // Map from artifactName --> list of resolutions found on the tree
        final SortedMap resolutionMap = Collections.synchronizedSortedMap(new TreeMap());
        final List futures = new ArrayList();
        LOG.debug("Using parallel dependency resolution: " + useParallelDependencyResolution);

        for (final Iterator iter = project.getDependencies().iterator(); iter.hasNext();) {
            final Dependency dependency = (Dependency) iter.next();

            if (useParallelDependencyResolution) {
                futures.add(executorService.submit(new Runnable() {
                    public void run() {
                        try {
                            updateResolutionMapForDep(visibleScopes, transitiveScopes, resolutionMap, dependency);
                        } catch (Exception e) {
                            Throwables.propagate(e);
                        }
                    }
                }));
            } else {
                updateResolutionMapForDep(visibleScopes, transitiveScopes, resolutionMap, dependency);
            }
        }
        if (useParallelDependencyResolution) {
            try {
                Futures.allAsList(futures).get();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                Throwables.propagate(e);
            } catch (ExecutionException e) {
                Throwables.propagate(e);
            }
        }
        return resolutionMap;
    }

    private void updateResolutionMapForDep(String[] visibleScopes, String[] transitiveScopes,
            SortedMap resolutionMap, Dependency dependency) throws InvalidDependencyVersionException,
            ProjectBuildingException, ArtifactResolutionException, ArtifactNotFoundException {
        LOG.debug("Checking direct dependency {}...", dependency);
        if (!isVisible(dependency.getScope(), visibleScopes)) {
            LOG.debug("... in invisible scope, ignoring!");
            return;
        }

        LOG.debug("... visible, resolving");

        // Dependency is visible, now resolve it.
        final String artifactName = getQualifiedName(dependency);

        if (artifactName.length() > maxLen) {
            maxLen = artifactName.length();
        }

        final Artifact resolvedArtifact = (Artifact) resolvedDependenciesByName.get(artifactName);

        if (resolvedArtifact == null) {
            // This is a potential problem because it should not be possible that a dependency that is required
            // by the project is not in the list of resolved dependencies.
            LOG.warn("No artifact available for '{}' (probably a multi-module child artifact).", artifactName);
        } else {
            final VersionResolution resolution = resolveVersion(dependency, resolvedArtifact, artifactName, true);
            addToResolutionMap(resolutionMap, resolution);

            if (!ArrayUtils.isEmpty(transitiveScopes)) {

                final ArtifactFilter scopeFilter = new ArtifactScopeFilter(transitiveScopes);

                // List of VersionResolution objects.
                List transitiveDependencies = null;

                try {
                    transitiveDependencies = resolveTransitiveVersions(dependency, resolvedArtifact, artifactName,
                            scopeFilter);
                } catch (MultipleArtifactsNotFoundException ex) {
                    logArtifactResolutionException(ex);
                    transitiveDependencies = resolveTransitiveVersions(dependency, ex.getResolvedArtifacts(),
                            artifactName, scopeFilter);
                } catch (AbstractArtifactResolutionException ex) {
                    logArtifactResolutionException(ex);
                }

                if (transitiveDependencies != null) {
                    LOG.debug("Artifact {} contributes {}", artifactName, transitiveDependencies);
                    for (Iterator transitiveIt = transitiveDependencies.iterator(); transitiveIt.hasNext();) {
                        final VersionResolution versionResolution = (VersionResolution) transitiveIt.next();
                        addToResolutionMap(resolutionMap, versionResolution);
                    }
                }
            }
        }
    }

    /**
     * Returns true if a given scope is available in the list of scopes.
     */
    private boolean isVisible(final String scope, final String[] validScopes) {
        for (int i = 0; i < validScopes.length; i++) {
            if (scope.equals(validScopes[i])) {
                return true;
            }
        }
        return false;
    }

    /**
     * Convenience method for a multi map add. Also makes sure that all the actual versions line up.
     */
    private void addToResolutionMap(final Map resolutionMap, final VersionResolution resolution) {
        Lock lock = (Lock) resolutionMapLocks.get(resolution.getDependencyName());
        // lock to protect mutation on the list per dependency as this can potentially run in multiple threads
        lock.lock();
        try {
            List resolutions = (List) resolutionMap.get(resolution.getDependencyName());
            if (resolutions == null) {
                resolutions = new ArrayList();
                resolutionMap.put(resolution.getDependencyName(), resolutions);
            }

            for (Iterator it = resolutions.iterator(); it.hasNext();) {
                final VersionResolution existingResolution = (VersionResolution) it.next();
                // TODO: It might be reasonable to fail the build in this case. However, I have yet to see
                // this message... :-)
                if (!existingResolution.getActualVersion().equals(resolution.getActualVersion())) {
                    LOG.warn("Dependency '{} expects version '{}' but '{}' already resolved to '{}'!",
                            new Object[] { resolution.getDependencyName(), resolution.getActualVersion(),
                                    existingResolution.getDependencyName(),
                                    existingResolution.getActualVersion() });
                }
            }
            LOG.debug("Adding resolution: {}", resolution);
            resolutions.add(resolution);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Create a version resolution for the given dependency and artifact.
     */
    private VersionResolution resolveVersion(Dependency dependency, Artifact artifact, String artifactName,
            final boolean directArtifact) {
        VersionResolution resolution = null;

        try {
            // Build a version from the artifact that was resolved.
            ArtifactVersion resolvedVersion = artifact.getSelectedVersion();

            if (resolvedVersion == null) {
                resolvedVersion = new DefaultArtifactVersion(artifact.getVersion());
            }

            // versionRange represents the versions that will satisfy the dependency.
            VersionRange versionRange = VersionRange.createFromVersionSpec(dependency.getVersion());
            // expectedVersion is the version declared in the dependency.
            ArtifactVersion expectedVersion = versionRange.getRecommendedVersion();

            if (expectedVersion == null) {
                // Fall back to the artifact version if it fits.
                if (versionRange.containsVersion(resolvedVersion)) {
                    expectedVersion = resolvedVersion;
                } else {
                    LOG.error(
                            "Cannot determine the recommended version of dependency '{}'; its version specification is '{}', and the resolved version is '{}'.",
                            new Object[] { artifactName, dependency.getVersion(), resolvedVersion.toString() });
                    return null;
                }
            }

            // Build internal versions
            final Version resolvedVersionObj = new Version(resolvedVersion.toString());
            final Version depVersionObj = new Version(versionRange.toString(), expectedVersion.toString());

            resolution = new VersionResolution(artifactName, artifactName, depVersionObj, resolvedVersionObj,
                    directArtifact);

            if (!isExcluded(artifact, depVersionObj, resolvedVersionObj)) {
                final Strategy strategy = findStrategy(resolution);

                if (!(versionRange.containsVersion(resolvedVersion)
                        && strategy.isCompatible(resolvedVersionObj, depVersionObj))) {
                    resolution.setConflict(true);
                }
            }
        } catch (InvalidVersionSpecificationException ex) {
            LOG.warn("Could not parse the version specification of an artifact", ex);
        } catch (OverConstrainedVersionException ex) {
            LOG.warn("Could not resolve an artifact", ex);
        }
        return resolution;
    }

    /**
     * Resolve all transitive dependencies relative to a given dependency, based off the artifact given. A scope filter can be added which limits the
     * results to the scopes present in that filter.
     */
    private List resolveTransitiveVersions(final Dependency dependency, final Artifact artifact,
            final String artifactName, final ArtifactFilter scopeFilter) throws InvalidDependencyVersionException,
            ArtifactResolutionException, ArtifactNotFoundException, ProjectBuildingException {
        ArtifactFilter exclusionFilter = null;

        if (!CollectionUtils.isEmpty(dependency.getExclusions())) {
            final List exclusions = new ArrayList();
            for (Iterator j = dependency.getExclusions().iterator(); j.hasNext();) {
                final Exclusion e = (Exclusion) j.next();
                exclusions.add(e.getGroupId() + ":" + e.getArtifactId());
            }

            exclusionFilter = new ExcludesArtifactFilter(exclusions);
            LOG.debug("Built Exclusion Filter: {}", exclusions);
        }

        final ArtifactFilter filter;
        if (exclusionFilter != null) {
            AndArtifactFilter andFilter = new AndArtifactFilter();
            andFilter.add(exclusionFilter);
            andFilter.add(scopeFilter);
            filter = andFilter;
        } else {
            filter = scopeFilter;
        }

        final Collection dependenciesToCheck = resolveDependenciesInItsOwnScope(artifact, filter);

        return resolveTransitiveVersions(dependency, dependenciesToCheck, artifactName, scopeFilter);
    }

    /**
     * Resolve all transitive dependencies relative to a given dependency, based off the list of artifacts given. A scope filter can be added which limits the
     * results to the scopes present in that filter.
     */
    private List resolveTransitiveVersions(final Dependency dependency, final Collection dependenciesToCheck,
            final String artifactName, final ArtifactFilter scopeFilter) throws InvalidDependencyVersionException,
            ArtifactResolutionException, ArtifactNotFoundException, ProjectBuildingException {
        final List resolutions = new ArrayList();

        for (Iterator dependenciesToCheckIter = dependenciesToCheck.iterator(); dependenciesToCheckIter
                .hasNext();) {
            Artifact dependencyArtifactToCheck = (Artifact) dependenciesToCheckIter.next();
            LOG.debug("Checking {}...", dependencyArtifactToCheck);
            if (!scopeFilter.include(dependencyArtifactToCheck)) {
                LOG.debug("... in invisible scope, ignoring!");
                continue; // for
            }

            LOG.debug("... visible ...");

            if (dependencyArtifactToCheck.isOptional()) {
                LOG.debug("... but optional, ignoring!");
                continue;
            }

            LOG.debug("... resolving!");

            String artifactToCheckName = getQualifiedName(dependencyArtifactToCheck);

            if (artifactToCheckName.length() > maxLen) {
                maxLen = artifactToCheckName.length();
            }

            Artifact resolvedDependency = (Artifact) resolvedDependenciesByName.get(artifactToCheckName);

            if (resolvedDependency == null) {
                LOG.debug("Dependency {}:{} of artifact {} is no longer used in the current project.",
                        new Object[] { artifactToCheckName, dependencyArtifactToCheck.getVersion(), artifactName });
            } else {
                // if the artifact in question is excluded in the current pom, then we don't have to worry about it anyways
                // this should be in the resolver. CHECKME! if (!exclusions.contains(dependencyArtifactToCheck.getGroupId() + ":" + dependencyArtifactToCheck.getArtifactId())) {
                final Version resolvedVersion = getVersion(resolvedDependency);
                final Version versionToCheck = getVersion(dependencyArtifactToCheck);

                final VersionResolution resolution = new VersionResolution(artifactName, artifactToCheckName,
                        versionToCheck, resolvedVersion, false);

                resolutions.add(resolution);

                // we have an error if
                // - if resolved dependency has a lower version or different qualifier than the stated one of the current transitive dependency
                // - if resolver dependency has a higher major version than the stated one of the current transitive dependency and
                // there is no explicit dependency to that major version in the current project
                // for this last check, we assume that explicit dependencies have already been checked against actual ones, so we only need to check
                // if the artifact is an explicit dependency

                final Strategy strategy = findStrategy(resolution);
                if (!isExcluded(resolvedDependency, versionToCheck, resolvedVersion)) {
                    if (!strategy.isCompatible(resolvedVersion, versionToCheck)) {
                        resolution.setConflict(true);
                    }
                } else if (warnIfMajorVersionIsHigher && !strategy.isCompatible(resolvedVersion, versionToCheck)) {
                    LOG.warn(
                            "Artifact {} depends on {} at an incompatible version ({}) than the current project ({})!",
                            new Object[] { artifactName, artifactToCheckName,
                                    dependencyArtifactToCheck.getVersion(), resolvedDependency.getVersion() });
                }
            }
        }

        return resolutions;
    }

    /**
     * Makes sure that all the exclusions are valid. They are called "Exception" for historical reasons.
     */
    private void checkExceptions() throws MojoExecutionException {
        if (!ArrayUtils.isEmpty(exceptions)) {
            for (int idx = 0; idx < exceptions.length; idx++) {
                if (exceptions[idx].check()) {
                    LOG.info("Adding exclusion '{}'", exceptions[idx]);
                } else {
                    throw new MojoExecutionException(
                            "Illegal exclusion specification " + exceptions[idx].toString());
                }
            }
        }
    }

    /**
     * Returns true if a given artifact and version are excluded from checking.
     */
    private boolean isExcluded(Artifact artifact, Version expectedVersion, Version resolvedVersion) {
        if (exceptions != null) {
            for (int idx = 0; idx < exceptions.length; idx++) {
                if (exceptions[idx].matches(artifact, expectedVersion, resolvedVersion)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns a Set of artifacts based off the given project. Artifacts can be filtered and optional dependencies can be excluded.
     *
     * It would be awesome if this method would also use the DependencyTreeBuilder which seems to yield better results (and is much closer to the actual compile tree in some cases)
     * than the artifactResolver. However, due to MNG-3236 the artifact filter is not applied when resolving dependencies and this method relies on the artifact filter to get
     * the scoping right. Well, maybe in maven 3.0 this will be better. Or different. Whatever comes first.
     */
    private Set resolveDependenciesInItsOwnScope(final MavenProject project, final ArtifactFilter filter,
            final boolean includeOptional)
            throws InvalidDependencyVersionException, ArtifactResolutionException, ArtifactNotFoundException {
        Set dependencyArtifacts = MavenMetadataSource.createArtifacts(artifactFactory, project.getDependencies(),
                null, filter, null);

        ArtifactResolutionResult result = artifactResolver.resolveTransitively(dependencyArtifacts,
                project.getArtifact(), Collections.EMPTY_MAP, localRepository,
                project.getRemoteArtifactRepositories(), artifactMetadataSource,
                new ArtifactOptionalFilter(includeOptional));

        return result.getArtifacts();
    }

    /**
     * Returns a Set of artifacts based off another artifact. The list of artifacts resolved can be filtered.
     *
     * It would be awesome if this method would also use the DependencyTreeBuilder which seems to yield better results (and is much closer to the actual compile tree in some cases)
     * than the artifactResolver. However, due to MNG-3236 the artifact filter is not applied when resolving dependencies and this method relies on the artifact filter to get
     * the scoping right. Well, maybe in maven 3.0 this will be better. Or different. Whatever comes first.
     */
    private Set resolveDependenciesInItsOwnScope(final Artifact artifact, final ArtifactFilter filter)
            throws InvalidDependencyVersionException, ArtifactResolutionException, ArtifactNotFoundException,
            ProjectBuildingException {
        MavenProject projectForArtifact = mavenProjectBuilder.buildFromRepository(artifact, remoteRepositories,
                localRepository);

        // "false" == do not include any optional dependencies from here. As these dependencies are off an artifact that is already a dependency, this
        // needs to ignore all optional deps. This avoids downloading poms that might not even exist and should not be part of the dependency
        // resolution of the main project.
        return resolveDependenciesInItsOwnScope(projectForArtifact, filter, false);
    }

    /**
     * Return a version object for an Artifact.
     */
    private Version getVersion(Artifact artifact) throws OverConstrainedVersionException {
        Version version = null;

        if (artifact != null) {
            if ((artifact.getVersionRange() != null) && (artifact.getSelectedVersion() != null)) {
                version = new Version(artifact.getVersionRange().toString(),
                        artifact.getSelectedVersion().toString());
            } else {
                version = new Version(artifact.getVersion());
            }
        }
        return version;
    }

    /**
     * Returns the qualified name for an Artifact.
     */
    private String getQualifiedName(Artifact artifact) {
        String result = artifact.getGroupId() + ":" + artifact.getArtifactId();

        if ((artifact.getType() != null) && !"jar".equals(artifact.getType())) {
            result = result + ":" + artifact.getType();
        }
        if ((artifact.getClassifier() != null)
                && (!"tests".equals(artifact.getClassifier()) || !"test-jar".equals(artifact.getType()))) {
            result = result + ":" + artifact.getClassifier();
        }
        return result;
    }

    /**
     * Returns the qualified name for a Dependency.
     */
    private String getQualifiedName(Dependency dependency) {
        String result = dependency.getGroupId() + ":" + dependency.getArtifactId();

        if ((dependency.getType() != null) && !"jar".equals(dependency.getType())) {
            result = result + ":" + dependency.getType();
        }
        if ((dependency.getClassifier() != null)
                && (!"tests".equals(dependency.getClassifier()) || !"test-jar".equals(dependency.getType()))) {
            result = result + ":" + dependency.getClassifier();
        }
        return result;
    }

    /**
     * Reports an exception thrown by the resolution process.
     */
    private void logArtifactResolutionException(AbstractArtifactResolutionException ex) {
        if (ex instanceof MultipleArtifactsNotFoundException) {
            MultipleArtifactsNotFoundException multiEx = (MultipleArtifactsNotFoundException) ex;
            StringBuilder builder = new StringBuilder();

            for (Iterator iter = multiEx.getMissingArtifacts().iterator(); iter.hasNext();) {
                Artifact artifact = (Artifact) iter.next();
                builder.append(getQualifiedName(artifact));

                if (iter.hasNext()) {
                    builder.append(", ");
                }
            }
            LOG.warn("Could not find artifacts '{}'", builder);
        } else {
            LOG.warn("Could not find artifact '{}'", getQualifiedName(ex.getArtifact()));
        }
        LOG.debug("Error:", ex);
    }
}