org.apache.maven.model.validation.DefaultModelValidator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.maven.model.validation.DefaultModelValidator.java

Source

package org.apache.maven.model.validation;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.
 */

import org.apache.maven.model.Activation;
import org.apache.maven.model.ActivationFile;
import org.apache.maven.model.Build;
import org.apache.maven.model.BuildBase;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.model.DistributionManagement;
import org.apache.maven.model.Exclusion;
import org.apache.maven.model.InputLocation;
import org.apache.maven.model.InputLocationTracker;
import org.apache.maven.model.Model;
import org.apache.maven.model.Parent;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.model.PluginManagement;
import org.apache.maven.model.Profile;
import org.apache.maven.model.ReportPlugin;
import org.apache.maven.model.Reporting;
import org.apache.maven.model.Repository;
import org.apache.maven.model.Resource;
import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.model.building.ModelProblem.Severity;
import org.apache.maven.model.building.ModelProblem.Version;
import org.apache.maven.model.building.ModelProblemCollector;
import org.apache.maven.model.building.ModelProblemCollectorRequest;
import org.apache.maven.model.interpolation.AbstractStringBasedModelInterpolator;
import org.codehaus.plexus.util.StringUtils;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Named;
import javax.inject.Singleton;

/**
 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
 */
@Named
@Singleton
public class DefaultModelValidator implements ModelValidator {

    private static final Pattern CI_FRIENDLY_EXPRESSION = Pattern.compile("\\$\\{(.+?)\\}");

    private static final List<String> CI_FRIENDLY_POSSIBLE_PROPERTY_NAMES = Arrays.asList(
            AbstractStringBasedModelInterpolator.REVISION_PROPERTY,
            AbstractStringBasedModelInterpolator.CHANGELIST_PROPERTY,
            AbstractStringBasedModelInterpolator.SHA1_PROPERTY);

    private static final String ILLEGAL_FS_CHARS = "\\/:\"<>|?*";

    private static final String ILLEGAL_VERSION_CHARS = ILLEGAL_FS_CHARS;

    private static final String ILLEGAL_REPO_ID_CHARS = ILLEGAL_FS_CHARS;

    private static final String EMPTY = "";

    private final Set<String> validIds = new HashSet<>();

    @Override
    public void validateRawModel(Model m, ModelBuildingRequest request, ModelProblemCollector problems) {
        Parent parent = m.getParent();
        if (parent != null) {
            validateStringNotEmpty("parent.groupId", problems, Severity.FATAL, Version.BASE, parent.getGroupId(),
                    parent);

            validateStringNotEmpty("parent.artifactId", problems, Severity.FATAL, Version.BASE,
                    parent.getArtifactId(), parent);

            validateStringNotEmpty("parent.version", problems, Severity.FATAL, Version.BASE, parent.getVersion(),
                    parent);

            if (equals(parent.getGroupId(), m.getGroupId()) && equals(parent.getArtifactId(), m.getArtifactId())) {
                addViolation(problems, Severity.FATAL, Version.BASE, "parent.artifactId", null,
                        "must be changed"
                                + ", the parent element cannot have the same groupId:artifactId as the project.",
                        parent);
            }

            if (equals("LATEST", parent.getVersion()) || equals("RELEASE", parent.getVersion())) {
                addViolation(problems, Severity.WARNING, Version.BASE, "parent.version", null,
                        "is either LATEST or RELEASE (both of them are being deprecated)", parent);
            }

        }

        if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
            Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);

            // [MNG-6074] Maven should produce an error if no model version has been set in a POM file used to build an
            // effective model.
            //
            // As of 3.4, the model version is mandatory even in raw models. The XML element still is optional in the
            // XML schema and this will not change anytime soon. We do not want to build effective models based on
            // models without a version starting with 3.4.
            validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.V20, m.getModelVersion(), m);

            validateModelVersion(problems, m.getModelVersion(), m, "4.0.0");

            validateStringNoExpression("groupId", problems, Severity.WARNING, Version.V20, m.getGroupId(), m);
            if (parent == null) {
                validateStringNotEmpty("groupId", problems, Severity.FATAL, Version.V20, m.getGroupId(), m);
            }

            validateStringNoExpression("artifactId", problems, Severity.WARNING, Version.V20, m.getArtifactId(), m);
            validateStringNotEmpty("artifactId", problems, Severity.FATAL, Version.V20, m.getArtifactId(), m);

            validateVersionNoExpression("version", problems, Severity.WARNING, Version.V20, m.getVersion(), m);
            if (parent == null) {
                validateStringNotEmpty("version", problems, Severity.FATAL, Version.V20, m.getVersion(), m);
            }

            validate20RawDependencies(problems, m.getDependencies(), "dependencies.dependency.", EMPTY, request);

            validate20RawDependenciesSelfReferencing(problems, m, m.getDependencies(), "dependencies.dependency",
                    request);

            if (m.getDependencyManagement() != null) {
                validate20RawDependencies(problems, m.getDependencyManagement().getDependencies(),
                        "dependencyManagement.dependencies.dependency.", EMPTY, request);
            }

            validateRawRepositories(problems, m.getRepositories(), "repositories.repository.", EMPTY, request);

            validateRawRepositories(problems, m.getPluginRepositories(), "pluginRepositories.pluginRepository.",
                    EMPTY, request);

            Build build = m.getBuild();
            if (build != null) {
                validate20RawPlugins(problems, build.getPlugins(), "build.plugins.plugin.", EMPTY, request);

                PluginManagement mgmt = build.getPluginManagement();
                if (mgmt != null) {
                    validate20RawPlugins(problems, mgmt.getPlugins(), "build.pluginManagement.plugins.plugin.",
                            EMPTY, request);
                }
            }

            Set<String> profileIds = new HashSet<>();

            for (Profile profile : m.getProfiles()) {
                String prefix = "profiles.profile[" + profile.getId() + "].";

                if (!profileIds.add(profile.getId())) {
                    addViolation(problems, errOn30, Version.V20, "profiles.profile.id", null,
                            "must be unique but found duplicate profile with id " + profile.getId(), profile);
                }

                validate30RawProfileActivation(problems, profile.getActivation(), profile.getId(), prefix,
                        "activation", request);

                validate20RawDependencies(problems, profile.getDependencies(), prefix, "dependencies.dependency.",
                        request);

                if (profile.getDependencyManagement() != null) {
                    validate20RawDependencies(problems, profile.getDependencyManagement().getDependencies(), prefix,
                            "dependencyManagement.dependencies.dependency.", request);
                }

                validateRawRepositories(problems, profile.getRepositories(), prefix, "repositories.repository.",
                        request);

                validateRawRepositories(problems, profile.getPluginRepositories(), prefix,
                        "pluginRepositories.pluginRepository.", request);

                BuildBase buildBase = profile.getBuild();
                if (buildBase != null) {
                    validate20RawPlugins(problems, buildBase.getPlugins(), prefix, "plugins.plugin.", request);

                    PluginManagement mgmt = buildBase.getPluginManagement();
                    if (mgmt != null) {
                        validate20RawPlugins(problems, mgmt.getPlugins(), prefix,
                                "pluginManagement.plugins.plugin.", request);
                    }
                }
            }
        }
    }

    private void validate30RawProfileActivation(ModelProblemCollector problems, Activation activation,
            String sourceHint, String prefix, String fieldName, ModelBuildingRequest request) {
        if (activation == null) {
            return;
        }

        ActivationFile file = activation.getFile();

        if (file != null) {
            String path;
            boolean missing;

            if (StringUtils.isNotEmpty(file.getExists())) {
                path = file.getExists();
                missing = false;
            } else if (StringUtils.isNotEmpty(file.getMissing())) {
                path = file.getMissing();
                missing = true;
            } else {
                return;
            }

            if (path.contains("${project.basedir}")) {
                addViolation(problems, Severity.WARNING, Version.V30,
                        prefix + fieldName + (missing ? ".file.missing" : ".file.exists"), null,
                        "Failed to interpolate file location " + path + " for profile " + sourceHint
                                + ": ${project.basedir} expression not supported during profile activation, "
                                + "use ${basedir} instead",
                        file.getLocation(missing ? "missing" : "exists"));
            } else if (hasProjectExpression(path)) {
                addViolation(problems, Severity.WARNING, Version.V30,
                        prefix + fieldName + (missing ? ".file.missing" : ".file.exists"), null,
                        "Failed to interpolate file location " + path + " for profile " + sourceHint
                                + ": ${project.*} expressions are not supported during profile activation",
                        file.getLocation(missing ? "missing" : "exists"));
            }
        }
    }

    private void validate20RawPlugins(ModelProblemCollector problems, List<Plugin> plugins, String prefix,
            String prefix2, ModelBuildingRequest request) {
        Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);

        Map<String, Plugin> index = new HashMap<>();

        for (Plugin plugin : plugins) {
            if (plugin.getGroupId() == null
                    || (plugin.getGroupId() != null && plugin.getGroupId().trim().isEmpty())) {
                addViolation(problems, Severity.FATAL, Version.V20, prefix + prefix2 + "(groupId:artifactId)", null,
                        "groupId of a plugin must be defined. ", plugin);
            }

            if (plugin.getArtifactId() == null
                    || (plugin.getArtifactId() != null && plugin.getArtifactId().trim().isEmpty())) {
                addViolation(problems, Severity.FATAL, Version.V20, prefix + prefix2 + "(groupId:artifactId)", null,
                        "artifactId of a plugin must be defined. ", plugin);
            }

            // This will catch cases like <version></version> or <version/>
            if (plugin.getVersion() != null && plugin.getVersion().trim().isEmpty()) {
                addViolation(problems, Severity.FATAL, Version.V20, prefix + prefix2 + "(groupId:artifactId)", null,
                        "version of a plugin must be defined. ", plugin);
            }

            String key = plugin.getKey();

            Plugin existing = index.get(key);

            if (existing != null) {
                addViolation(problems, errOn31, Version.V20, prefix + prefix2 + "(groupId:artifactId)", null,
                        "must be unique but found duplicate declaration of plugin " + key, plugin);
            } else {
                index.put(key, plugin);
            }

            Set<String> executionIds = new HashSet<>();

            for (PluginExecution exec : plugin.getExecutions()) {
                if (!executionIds.add(exec.getId())) {
                    addViolation(problems, Severity.ERROR, Version.V20,
                            prefix + prefix2 + "[" + plugin.getKey() + "].executions.execution.id", null,
                            "must be unique but found duplicate execution with id " + exec.getId(), exec);
                }
            }
        }
    }

    @Override
    public void validateEffectiveModel(Model m, ModelBuildingRequest request, ModelProblemCollector problems) {
        validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.BASE, m.getModelVersion(), m);

        validateId("groupId", problems, m.getGroupId(), m);

        validateId("artifactId", problems, m.getArtifactId(), m);

        validateStringNotEmpty("packaging", problems, Severity.ERROR, Version.BASE, m.getPackaging(), m);

        if (!m.getModules().isEmpty()) {
            if (!"pom".equals(m.getPackaging())) {
                addViolation(problems, Severity.ERROR, Version.BASE, "packaging", null, "with value '"
                        + m.getPackaging() + "' is invalid. Aggregator projects " + "require 'pom' as packaging.",
                        m);
            }

            for (int i = 0, n = m.getModules().size(); i < n; i++) {
                String module = m.getModules().get(i);
                if (StringUtils.isBlank(module)) {
                    addViolation(problems, Severity.ERROR, Version.BASE, "modules.module[" + i + "]", null,
                            "has been specified without a path to the project directory.",
                            m.getLocation("modules"));
                }
            }
        }

        validateStringNotEmpty("version", problems, Severity.ERROR, Version.BASE, m.getVersion(), m);

        Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);

        validateEffectiveDependencies(problems, m, m.getDependencies(), false, request);

        DependencyManagement mgmt = m.getDependencyManagement();
        if (mgmt != null) {
            validateEffectiveDependencies(problems, m, mgmt.getDependencies(), true, request);
        }

        if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
            Set<String> modules = new HashSet<>();
            for (int i = 0, n = m.getModules().size(); i < n; i++) {
                String module = m.getModules().get(i);
                if (!modules.add(module)) {
                    addViolation(problems, Severity.ERROR, Version.V20, "modules.module[" + i + "]", null,
                            "specifies duplicate child module " + module, m.getLocation("modules"));
                }
            }

            Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);

            validateBannedCharacters(EMPTY, "version", problems, errOn31, Version.V20, m.getVersion(), null, m,
                    ILLEGAL_VERSION_CHARS);
            validate20ProperSnapshotVersion("version", problems, errOn31, Version.V20, m.getVersion(), null, m);

            Build build = m.getBuild();
            if (build != null) {
                for (Plugin p : build.getPlugins()) {
                    validateStringNotEmpty("build.plugins.plugin.artifactId", problems, Severity.ERROR, Version.V20,
                            p.getArtifactId(), p);

                    validateStringNotEmpty("build.plugins.plugin.groupId", problems, Severity.ERROR, Version.V20,
                            p.getGroupId(), p);

                    validate20PluginVersion("build.plugins.plugin.version", problems, p.getVersion(), p.getKey(), p,
                            request);

                    validateBoolean("build.plugins.plugin.inherited", EMPTY, problems, errOn30, Version.V20,
                            p.getInherited(), p.getKey(), p);

                    validateBoolean("build.plugins.plugin.extensions", EMPTY, problems, errOn30, Version.V20,
                            p.getExtensions(), p.getKey(), p);

                    validate20EffectivePluginDependencies(problems, p, request);
                }

                validate20RawResources(problems, build.getResources(), "build.resources.resource.", request);

                validate20RawResources(problems, build.getTestResources(), "build.testResources.testResource.",
                        request);
            }

            Reporting reporting = m.getReporting();
            if (reporting != null) {
                for (ReportPlugin p : reporting.getPlugins()) {
                    validateStringNotEmpty("reporting.plugins.plugin.artifactId", problems, Severity.ERROR,
                            Version.V20, p.getArtifactId(), p);

                    validateStringNotEmpty("reporting.plugins.plugin.groupId", problems, Severity.ERROR,
                            Version.V20, p.getGroupId(), p);
                }
            }

            for (Repository repository : m.getRepositories()) {
                validate20EffectiveRepository(problems, repository, "repositories.repository.", request);
            }

            for (Repository repository : m.getPluginRepositories()) {
                validate20EffectiveRepository(problems, repository, "pluginRepositories.pluginRepository.",
                        request);
            }

            DistributionManagement distMgmt = m.getDistributionManagement();
            if (distMgmt != null) {
                if (distMgmt.getStatus() != null) {
                    addViolation(problems, Severity.ERROR, Version.V20, "distributionManagement.status", null,
                            "must not be specified.", distMgmt);
                }

                validate20EffectiveRepository(problems, distMgmt.getRepository(),
                        "distributionManagement.repository.", request);
                validate20EffectiveRepository(problems, distMgmt.getSnapshotRepository(),
                        "distributionManagement.snapshotRepository.", request);
            }
        }
    }

    private void validate20RawDependencies(ModelProblemCollector problems, List<Dependency> dependencies,
            String prefix, String prefix2, ModelBuildingRequest request) {
        Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
        Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);

        Map<String, Dependency> index = new HashMap<>();

        for (Dependency dependency : dependencies) {
            String key = dependency.getManagementKey();

            if ("import".equals(dependency.getScope())) {
                if (!"pom".equals(dependency.getType())) {
                    addViolation(problems, Severity.WARNING, Version.V20, prefix + prefix2 + "type", key,
                            "must be 'pom' to import the managed dependencies.", dependency);
                } else if (StringUtils.isNotEmpty(dependency.getClassifier())) {
                    addViolation(problems, errOn30, Version.V20, prefix + prefix2 + "classifier", key,
                            "must be empty, imported POM cannot have a classifier.", dependency);
                }
            } else if ("system".equals(dependency.getScope())) {

                if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1) {
                    addViolation(problems, Severity.WARNING, Version.V31, prefix + prefix2 + "scope", key,
                            "declares usage of deprecated 'system' scope ", dependency);
                }

                String sysPath = dependency.getSystemPath();
                if (StringUtils.isNotEmpty(sysPath)) {
                    if (!hasExpression(sysPath)) {
                        addViolation(problems, Severity.WARNING, Version.V20, prefix + prefix2 + "systemPath", key,
                                "should use a variable instead of a hard-coded path " + sysPath, dependency);
                    } else if (sysPath.contains("${basedir}") || sysPath.contains("${project.basedir}")) {
                        addViolation(problems, Severity.WARNING, Version.V20, prefix + prefix2 + "systemPath", key,
                                "should not point at files within the project directory, " + sysPath
                                        + " will be unresolvable by dependent projects",
                                dependency);
                    }
                }
            }

            if (equals("LATEST", dependency.getVersion()) || equals("RELEASE", dependency.getVersion())) {
                addViolation(problems, Severity.WARNING, Version.BASE, prefix + prefix2 + "version", key,
                        "is either LATEST or RELEASE (both of them are being deprecated)", dependency);
            }

            Dependency existing = index.get(key);

            if (existing != null) {
                String msg;
                if (equals(existing.getVersion(), dependency.getVersion())) {
                    msg = "duplicate declaration of version "
                            + StringUtils.defaultString(dependency.getVersion(), "(?)");
                } else {
                    msg = "version " + StringUtils.defaultString(existing.getVersion(), "(?)") + " vs "
                            + StringUtils.defaultString(dependency.getVersion(), "(?)");
                }

                addViolation(problems, errOn31, Version.V20,
                        prefix + prefix2 + "(groupId:artifactId:type:classifier)", null,
                        "must be unique: " + key + " -> " + msg, dependency);
            } else {
                index.put(key, dependency);
            }
        }
    }

    private void validate20RawDependenciesSelfReferencing(ModelProblemCollector problems, Model m,
            List<Dependency> dependencies, String prefix, ModelBuildingRequest request) {
        // We only check for groupId/artifactId/version/classifier cause if there is another
        // module with the same groupId/artifactId/version/classifier this will fail the build
        // earlier like "Project '...' is duplicated in the reactor.
        // So it is sufficient to check only groupId/artifactId/version/classifier and not the
        // packaging type.
        for (Dependency dependency : dependencies) {
            String key = dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getVersion()
                    + (dependency.getClassifier() != null ? ":" + dependency.getClassifier() : EMPTY);
            String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
            if (key.equals(mKey)) {
                // This means a module which is build has a dependency which has the same
                // groupId, artifactId, version and classifier coordinates. This is in consequence
                // a self reference or in other words a circular reference which can not being resolved.
                addViolation(problems, Severity.FATAL, Version.V31, prefix + "[" + key + "]", key,
                        "is referencing itself.", dependency);

            }
        }
    }

    private void validateEffectiveDependencies(ModelProblemCollector problems, Model m,
            List<Dependency> dependencies, boolean management, ModelBuildingRequest request) {
        Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);

        String prefix = management ? "dependencyManagement.dependencies.dependency." : "dependencies.dependency.";

        for (Dependency d : dependencies) {
            validateEffectiveDependency(problems, d, management, prefix, request);

            if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
                validateBoolean(prefix, "optional", problems, errOn30, Version.V20, d.getOptional(),
                        d.getManagementKey(), d);

                if (!management) {
                    validateVersion(prefix, "version", problems, errOn30, Version.V20, d.getVersion(),
                            d.getManagementKey(), d);

                    /*
                     * TODO Extensions like Flex Mojos use custom scopes like "merged", "internal", "external", etc. In
                     * order to don't break backward-compat with those, only warn but don't error out.
                     */
                    validateEnum(prefix, "scope", problems, Severity.WARNING, Version.V20, d.getScope(),
                            d.getManagementKey(), d, "provided", "compile", "runtime", "test", "system");

                    validateEffectiveModelAgainstDependency(prefix, problems, m, d, request);
                } else {
                    validateEnum(prefix, "scope", problems, Severity.WARNING, Version.V20, d.getScope(),
                            d.getManagementKey(), d, "provided", "compile", "runtime", "test", "system", "import");
                }
            }
        }
    }

    private void validateEffectiveModelAgainstDependency(String prefix, ModelProblemCollector problems, Model m,
            Dependency d, ModelBuildingRequest request) {
        String key = d.getGroupId() + ":" + d.getArtifactId() + ":" + d.getVersion()
                + (d.getClassifier() != null ? ":" + d.getClassifier() : EMPTY);
        String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
        if (key.equals(mKey)) {
            // This means a module which is build has a dependency which has the same
            // groupId, artifactId, version and classifier coordinates. This is in consequence
            // a self reference or in other words a circular reference which can not being resolved.
            addViolation(problems, Severity.FATAL, Version.V31, prefix + "[" + key + "]", key,
                    "is referencing itself.", d);

        }

    }

    private void validate20EffectivePluginDependencies(ModelProblemCollector problems, Plugin plugin,
            ModelBuildingRequest request) {
        List<Dependency> dependencies = plugin.getDependencies();

        if (!dependencies.isEmpty()) {
            String prefix = "build.plugins.plugin[" + plugin.getKey() + "].dependencies.dependency.";

            Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);

            for (Dependency d : dependencies) {
                validateEffectiveDependency(problems, d, false, prefix, request);

                validateVersion(prefix, "version", problems, errOn30, Version.BASE, d.getVersion(),
                        d.getManagementKey(), d);

                validateEnum(prefix, "scope", problems, errOn30, Version.BASE, d.getScope(), d.getManagementKey(),
                        d, "compile", "runtime", "system");
            }
        }
    }

    private void validateEffectiveDependency(ModelProblemCollector problems, Dependency d, boolean management,
            String prefix, ModelBuildingRequest request) {
        validateId(prefix, "artifactId", problems, Severity.ERROR, Version.BASE, d.getArtifactId(),
                d.getManagementKey(), d);

        validateId(prefix, "groupId", problems, Severity.ERROR, Version.BASE, d.getGroupId(), d.getManagementKey(),
                d);

        if (!management) {
            validateStringNotEmpty(prefix, "type", problems, Severity.ERROR, Version.BASE, d.getType(),
                    d.getManagementKey(), d);

            validateDependencyVersion(problems, d, prefix);
        }

        if ("system".equals(d.getScope())) {
            String systemPath = d.getSystemPath();

            if (StringUtils.isEmpty(systemPath)) {
                addViolation(problems, Severity.ERROR, Version.BASE, prefix + "systemPath", d.getManagementKey(),
                        "is missing.", d);
            } else {
                File sysFile = new File(systemPath);
                if (!sysFile.isAbsolute()) {
                    addViolation(problems, Severity.ERROR, Version.BASE, prefix + "systemPath",
                            d.getManagementKey(), "must specify an absolute path but is " + systemPath, d);
                } else if (!sysFile.isFile()) {
                    String msg = "refers to a non-existing file " + sysFile.getAbsolutePath();
                    systemPath = systemPath.replace('/', File.separatorChar).replace('\\', File.separatorChar);
                    String jdkHome = request.getSystemProperties().getProperty("java.home", EMPTY) + File.separator
                            + "..";
                    if (systemPath.startsWith(jdkHome)) {
                        msg += ". Please verify that you run Maven using a JDK and not just a JRE.";
                    }
                    addViolation(problems, Severity.WARNING, Version.BASE, prefix + "systemPath",
                            d.getManagementKey(), msg, d);
                }
            }
        } else if (StringUtils.isNotEmpty(d.getSystemPath())) {
            addViolation(problems, Severity.ERROR, Version.BASE, prefix + "systemPath", d.getManagementKey(),
                    "must be omitted." + " This field may only be specified for a dependency with system scope.",
                    d);
        }

        if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
            for (Exclusion exclusion : d.getExclusions()) {
                if (request.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0) {
                    validateId(prefix, "exclusions.exclusion.groupId", problems, Severity.WARNING, Version.V20,
                            exclusion.getGroupId(), d.getManagementKey(), exclusion);

                    validateId(prefix, "exclusions.exclusion.artifactId", problems, Severity.WARNING, Version.V20,
                            exclusion.getArtifactId(), d.getManagementKey(), exclusion);
                } else {
                    validateIdWithWildcards(prefix, "exclusions.exclusion.groupId", problems, Severity.WARNING,
                            Version.V30, exclusion.getGroupId(), d.getManagementKey(), exclusion);

                    validateIdWithWildcards(prefix, "exclusions.exclusion.artifactId", problems, Severity.WARNING,
                            Version.V30, exclusion.getArtifactId(), d.getManagementKey(), exclusion);
                }
            }
        }
    }

    /**
     * @since 3.2.4
     */
    protected void validateDependencyVersion(ModelProblemCollector problems, Dependency d, String prefix) {
        validateStringNotEmpty(prefix, "version", problems, Severity.ERROR, Version.BASE, d.getVersion(),
                d.getManagementKey(), d);
    }

    private void validateRawRepositories(ModelProblemCollector problems, List<Repository> repositories,
            String prefix, String prefix2, ModelBuildingRequest request) {
        Map<String, Repository> index = new HashMap<>();

        for (Repository repository : repositories) {
            validateStringNotEmpty(prefix, prefix2, "id", problems, Severity.ERROR, Version.V20, repository.getId(),
                    null, repository);

            validateStringNotEmpty(prefix, prefix2, "[" + repository.getId() + "].url", problems, Severity.ERROR,
                    Version.V20, repository.getUrl(), null, repository);

            String key = repository.getId();

            Repository existing = index.get(key);

            if (existing != null) {
                Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);

                addViolation(
                        problems, errOn30, Version.V20, prefix + prefix2 + "id", null, "must be unique: "
                                + repository.getId() + " -> " + existing.getUrl() + " vs " + repository.getUrl(),
                        repository);
            } else {
                index.put(key, repository);
            }
        }
    }

    private void validate20EffectiveRepository(ModelProblemCollector problems, Repository repository, String prefix,
            ModelBuildingRequest request) {
        if (repository != null) {
            Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);

            validateBannedCharacters(prefix, "id", problems, errOn31, Version.V20, repository.getId(), null,
                    repository, ILLEGAL_REPO_ID_CHARS);

            if ("local".equals(repository.getId())) {
                addViolation(problems, errOn31, Version.V20, prefix + "id", null,
                        "must not be 'local'" + ", this identifier is reserved for the local repository"
                                + ", using it for other repositories will corrupt your repository metadata.",
                        repository);
            }

            if ("legacy".equals(repository.getLayout())) {
                addViolation(problems, Severity.WARNING, Version.V20, prefix + "layout", repository.getId(),
                        "uses the unsupported value 'legacy', artifact resolution might fail.", repository);
            }
        }
    }

    private void validate20RawResources(ModelProblemCollector problems, List<Resource> resources, String prefix,
            ModelBuildingRequest request) {
        Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);

        for (Resource resource : resources) {
            validateStringNotEmpty(prefix, "directory", problems, Severity.ERROR, Version.V20,
                    resource.getDirectory(), null, resource);

            validateBoolean(prefix, "filtering", problems, errOn30, Version.V20, resource.getFiltering(),
                    resource.getDirectory(), resource);
        }
    }

    // ----------------------------------------------------------------------
    // Field validation
    // ----------------------------------------------------------------------

    private boolean validateId(String fieldName, ModelProblemCollector problems, String id,
            InputLocationTracker tracker) {
        return validateId(EMPTY, fieldName, problems, Severity.ERROR, Version.BASE, id, null, tracker);
    }

    @SuppressWarnings("checkstyle:parameternumber")
    private boolean validateId(String prefix, String fieldName, ModelProblemCollector problems, Severity severity,
            Version version, String id, String sourceHint, InputLocationTracker tracker) {
        if (validIds.contains(id)) {
            return true;
        }
        if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
            return false;
        } else {
            if (!isValidId(id)) {
                addViolation(problems, severity, version, prefix + fieldName, sourceHint,
                        "with value '" + id + "' does not match a valid id pattern.", tracker);
                return false;
            }
            validIds.add(id);
            return true;
        }
    }

    private boolean isValidId(String id) {
        for (int i = 0; i < id.length(); i++) {
            char c = id.charAt(i);
            if (!isValidIdCharacter(c)) {
                return false;
            }
        }
        return true;
    }

    private boolean isValidIdCharacter(char c) {
        return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '-' || c == '_'
                || c == '.';
    }

    @SuppressWarnings("checkstyle:parameternumber")
    private boolean validateIdWithWildcards(String prefix, String fieldName, ModelProblemCollector problems,
            Severity severity, Version version, String id, String sourceHint, InputLocationTracker tracker) {
        if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
            return false;
        } else {
            if (!isValidIdWithWildCards(id)) {
                addViolation(problems, severity, version, prefix + fieldName, sourceHint,
                        "with value '" + id + "' does not match a valid id pattern.", tracker);
                return false;
            }
            return true;
        }
    }

    private boolean isValidIdWithWildCards(String id) {
        for (int i = 0; i < id.length(); i++) {
            char c = id.charAt(i);
            if (!isValidIdWithWildCardCharacter(c)) {
                return false;
            }
        }
        return true;
    }

    private boolean isValidIdWithWildCardCharacter(char c) {
        return isValidIdCharacter(c) || c == '?' || c == '*';
    }

    private boolean validateStringNoExpression(String fieldName, ModelProblemCollector problems, Severity severity,
            Version version, String string, InputLocationTracker tracker) {
        if (!hasExpression(string)) {
            return true;
        }

        addViolation(problems, severity, version, fieldName, null,
                "contains an expression but should be a constant.", tracker);

        return false;
    }

    private boolean validateVersionNoExpression(String fieldName, ModelProblemCollector problems, Severity severity,
            Version version, String string, InputLocationTracker tracker) {
        if (!hasExpression(string)) {
            return true;
        }

        //
        // Acceptable versions for continuous delivery
        //
        // changelist
        // revision
        // sha1
        //
        Matcher m = CI_FRIENDLY_EXPRESSION.matcher(string.trim());
        while (m.find()) {
            if (!CI_FRIENDLY_POSSIBLE_PROPERTY_NAMES.contains(m.group(1))) {
                addViolation(problems, severity, version, fieldName, null,
                        "contains an expression but should be a constant.", tracker);

                return false;
            }
        }

        return true;
    }

    private boolean hasExpression(String value) {
        return value != null && value.contains("${");
    }

    private boolean hasProjectExpression(String value) {
        return value != null && value.contains("${project.");
    }

    private boolean validateStringNotEmpty(String fieldName, ModelProblemCollector problems, Severity severity,
            Version version, String string, InputLocationTracker tracker) {
        return validateStringNotEmpty(EMPTY, fieldName, problems, severity, version, string, null, tracker);
    }

    /**
     * Asserts:
     * <p/>
     * <ul>
     * <li><code>string != null</code>
     * <li><code>string.length > 0</code>
     * </ul>
     */
    @SuppressWarnings("checkstyle:parameternumber")
    private boolean validateStringNotEmpty(String prefix, String prefix2, String fieldName,
            ModelProblemCollector problems, Severity severity, Version version, String string, String sourceHint,
            InputLocationTracker tracker) {
        if (!validateNotNull(prefix, prefix2, fieldName, problems, severity, version, string, sourceHint,
                tracker)) {
            return false;
        }

        if (!string.isEmpty()) {
            return true;
        }

        addViolation(problems, severity, version, prefix + prefix2 + fieldName, sourceHint, "is missing.", tracker);

        return false;
    }

    /**
     * Asserts:
     * <p/>
     * <ul>
     * <li><code>string != null</code>
     * <li><code>string.length > 0</code>
     * </ul>
     */
    @SuppressWarnings("checkstyle:parameternumber")
    private boolean validateStringNotEmpty(String prefix, String fieldName, ModelProblemCollector problems,
            Severity severity, Version version, String string, String sourceHint, InputLocationTracker tracker) {
        if (!validateNotNull(prefix, fieldName, problems, severity, version, string, sourceHint, tracker)) {
            return false;
        }

        if (string.length() > 0) {
            return true;
        }

        addViolation(problems, severity, version, prefix + fieldName, sourceHint, "is missing.", tracker);

        return false;
    }

    /**
     * Asserts:
     * <p/>
     * <ul>
     * <li><code>string != null</code>
     * </ul>
     */
    @SuppressWarnings("checkstyle:parameternumber")
    private boolean validateNotNull(String prefix, String fieldName, ModelProblemCollector problems,
            Severity severity, Version version, Object object, String sourceHint, InputLocationTracker tracker) {
        if (object != null) {
            return true;
        }

        addViolation(problems, severity, version, prefix + fieldName, sourceHint, "is missing.", tracker);

        return false;
    }

    /**
     * Asserts:
     * <p/>
     * <ul>
     * <li><code>string != null</code>
     * </ul>
     */
    @SuppressWarnings("checkstyle:parameternumber")
    private boolean validateNotNull(String prefix, String prefix2, String fieldName, ModelProblemCollector problems,
            Severity severity, Version version, Object object, String sourceHint, InputLocationTracker tracker) {
        if (object != null) {
            return true;
        }

        addViolation(problems, severity, version, prefix + prefix2 + fieldName, sourceHint, "is missing.", tracker);

        return false;
    }

    @SuppressWarnings("checkstyle:parameternumber")
    private boolean validateBoolean(String prefix, String fieldName, ModelProblemCollector problems,
            Severity severity, Version version, String string, String sourceHint, InputLocationTracker tracker) {
        if (string == null || string.length() <= 0) {
            return true;
        }

        if ("true".equalsIgnoreCase(string) || "false".equalsIgnoreCase(string)) {
            return true;
        }

        addViolation(problems, severity, version, prefix + fieldName, sourceHint,
                "must be 'true' or 'false' but is '" + string + "'.", tracker);

        return false;
    }

    @SuppressWarnings("checkstyle:parameternumber")
    private boolean validateEnum(String prefix, String fieldName, ModelProblemCollector problems, Severity severity,
            Version version, String string, String sourceHint, InputLocationTracker tracker,
            String... validValues) {
        if (string == null || string.length() <= 0) {
            return true;
        }

        List<String> values = Arrays.asList(validValues);

        if (values.contains(string)) {
            return true;
        }

        addViolation(problems, severity, version, prefix + fieldName, sourceHint,
                "must be one of " + values + " but is '" + string + "'.", tracker);

        return false;
    }

    @SuppressWarnings("checkstyle:parameternumber")
    private boolean validateModelVersion(ModelProblemCollector problems, String string,
            InputLocationTracker tracker, String... validVersions) {
        if (string == null || string.length() <= 0) {
            return true;
        }

        List<String> values = Arrays.asList(validVersions);

        if (values.contains(string)) {
            return true;
        }

        boolean newerThanAll = true;
        boolean olderThanAll = true;
        for (String validValue : validVersions) {
            final int comparison = compareModelVersions(validValue, string);
            newerThanAll = newerThanAll && comparison < 0;
            olderThanAll = olderThanAll && comparison > 0;
        }

        if (newerThanAll) {
            addViolation(problems, Severity.FATAL, Version.V20, "modelVersion", null,
                    "of '" + string + "' is newer than the versions supported by this version of Maven: " + values
                            + ". Building this project requires a newer version of Maven.",
                    tracker);

        } else if (olderThanAll) {
            // note this will not be hit for Maven 1.x project.xml as it is an incompatible schema
            addViolation(problems, Severity.FATAL, Version.V20, "modelVersion", null,
                    "of '" + string + "' is older than the versions supported by this version of Maven: " + values
                            + ". Building this project requires an older version of Maven.",
                    tracker);

        } else {
            addViolation(problems, Severity.ERROR, Version.V20, "modelVersion", null,
                    "must be one of " + values + " but is '" + string + "'.", tracker);
        }

        return false;
    }

    /**
     * Compares two model versions.
     *
     * @param first the first version.
     * @param second the second version.
     * @return negative if the first version is newer than the second version, zero if they are the same or positive if
     * the second version is the newer.
     */
    private static int compareModelVersions(String first, String second) {
        // we use a dedicated comparator because we control our model version scheme.
        String[] firstSegments = StringUtils.split(first, ".");
        String[] secondSegments = StringUtils.split(second, ".");
        for (int i = 0; i < Math.min(firstSegments.length, secondSegments.length); i++) {
            int result = Long.valueOf(firstSegments[i]).compareTo(Long.valueOf(secondSegments[i]));
            if (result != 0) {
                return result;
            }
        }
        if (firstSegments.length == secondSegments.length) {
            return 0;
        }
        return firstSegments.length > secondSegments.length ? -1 : 1;
    }

    @SuppressWarnings("checkstyle:parameternumber")
    private boolean validateBannedCharacters(String prefix, String fieldName, ModelProblemCollector problems,
            Severity severity, Version version, String string, String sourceHint, InputLocationTracker tracker,
            String banned) {
        if (string != null) {
            for (int i = string.length() - 1; i >= 0; i--) {
                if (banned.indexOf(string.charAt(i)) >= 0) {
                    addViolation(problems, severity, version, prefix + fieldName, sourceHint,
                            "must not contain any of these characters " + banned + " but found " + string.charAt(i),
                            tracker);
                    return false;
                }
            }
        }

        return true;
    }

    @SuppressWarnings("checkstyle:parameternumber")
    private boolean validateVersion(String prefix, String fieldName, ModelProblemCollector problems,
            Severity severity, Version version, String string, String sourceHint, InputLocationTracker tracker) {
        if (string == null || string.length() <= 0) {
            return true;
        }

        if (hasExpression(string)) {
            addViolation(problems, severity, version, prefix + fieldName, sourceHint,
                    "must be a valid version but is '" + string + "'.", tracker);
            return false;
        }

        return validateBannedCharacters(prefix, fieldName, problems, severity, version, string, sourceHint, tracker,
                ILLEGAL_VERSION_CHARS);

    }

    private boolean validate20ProperSnapshotVersion(String fieldName, ModelProblemCollector problems,
            Severity severity, Version version, String string, String sourceHint, InputLocationTracker tracker) {
        if (string == null || string.length() <= 0) {
            return true;
        }

        if (string.endsWith("SNAPSHOT") && !string.endsWith("-SNAPSHOT")) {
            addViolation(problems, severity, version, fieldName, sourceHint,
                    "uses an unsupported snapshot version format, should be '*-SNAPSHOT' instead.", tracker);
            return false;
        }

        return true;
    }

    private boolean validate20PluginVersion(String fieldName, ModelProblemCollector problems, String string,
            String sourceHint, InputLocationTracker tracker, ModelBuildingRequest request) {
        if (string == null) {
            // NOTE: The check for missing plugin versions is handled directly by the model builder
            return true;
        }

        Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);

        if (!validateVersion(EMPTY, fieldName, problems, errOn30, Version.V20, string, sourceHint, tracker)) {
            return false;
        }

        if (string.length() <= 0 || "RELEASE".equals(string) || "LATEST".equals(string)) {
            addViolation(problems, errOn30, Version.V20, fieldName, sourceHint,
                    "must be a valid version but is '" + string + "'.", tracker);
            return false;
        }

        return true;
    }

    private static void addViolation(ModelProblemCollector problems, Severity severity, Version version,
            String fieldName, String sourceHint, String message, InputLocationTracker tracker) {
        StringBuilder buffer = new StringBuilder(256);
        buffer.append('\'').append(fieldName).append('\'');

        if (sourceHint != null) {
            buffer.append(" for ").append(sourceHint);
        }

        buffer.append(' ').append(message);

        // CHECKSTYLE_OFF: LineLength
        problems.add(new ModelProblemCollectorRequest(severity, version).setMessage(buffer.toString())
                .setLocation(getLocation(fieldName, tracker)));
        // CHECKSTYLE_ON: LineLength
    }

    private static InputLocation getLocation(String fieldName, InputLocationTracker tracker) {
        InputLocation location = null;

        if (tracker != null) {
            if (fieldName != null) {
                Object key = fieldName;

                int idx = fieldName.lastIndexOf('.');
                if (idx >= 0) {
                    fieldName = fieldName.substring(idx + 1);
                    key = fieldName;
                }

                if (fieldName.endsWith("]")) {
                    key = fieldName.substring(fieldName.lastIndexOf('[') + 1, fieldName.length() - 1);
                    try {
                        key = Integer.valueOf(key.toString());
                    } catch (NumberFormatException e) {
                        // use key as is
                    }
                }

                location = tracker.getLocation(key);
            }

            if (location == null) {
                location = tracker.getLocation(EMPTY);
            }
        }

        return location;
    }

    private static boolean equals(String s1, String s2) {
        return StringUtils.clean(s1).equals(StringUtils.clean(s2));
    }

    private static Severity getSeverity(ModelBuildingRequest request, int errorThreshold) {
        return getSeverity(request.getValidationLevel(), errorThreshold);
    }

    private static Severity getSeverity(int validationLevel, int errorThreshold) {
        if (validationLevel < errorThreshold) {
            return Severity.WARNING;
        } else {
            return Severity.ERROR;
        }
    }

}