Java tutorial
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ø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; } } }