| /* |
| * 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. |
| */ |
| package org.apache.maven.model.validation; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.inject.Singleton; |
| |
| import java.io.File; |
| import java.util.Arrays; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import java.util.function.Supplier; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| import java.util.stream.StreamSupport; |
| |
| import org.apache.maven.model.Activation; |
| 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.ModelVersionProcessor; |
| import org.codehaus.plexus.util.StringUtils; |
| |
| /** |
| * @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 Pattern EXPRESSION_PROJECT_NAME_PATTERN = Pattern.compile("\\$\\{(project.+?)}"); |
| |
| 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<>(); |
| |
| private ModelVersionProcessor versionProcessor; |
| |
| @Inject |
| public DefaultModelValidator(ModelVersionProcessor versionProcessor) { |
| this.versionProcessor = versionProcessor; |
| } |
| |
| @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(), prefix); |
| |
| 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 prefix) { |
| if (activation == null) { |
| return; |
| } |
| class ActivationFrame { |
| String location; |
| Optional<? extends InputLocationTracker> parent; |
| |
| ActivationFrame(String location, Optional<? extends InputLocationTracker> parent) { |
| this.location = location; |
| this.parent = parent; |
| } |
| } |
| final Deque<ActivationFrame> stk = new LinkedList<>(); |
| |
| final Supplier<String> pathSupplier = () -> { |
| final boolean parallel = false; |
| return StreamSupport.stream(((Iterable<ActivationFrame>) stk::descendingIterator).spliterator(), parallel) |
| .map(f -> f.location) |
| .collect(Collectors.joining(".")); |
| }; |
| final Supplier<InputLocation> locationSupplier = () -> { |
| if (stk.size() < 2) { |
| return null; |
| } |
| Iterator<ActivationFrame> f = stk.iterator(); |
| |
| String location = f.next().location; |
| ActivationFrame parent = f.next(); |
| |
| return parent.parent.map(p -> p.getLocation(location)).orElse(null); |
| }; |
| final Consumer<String> validator = s -> { |
| if (hasProjectExpression(s)) { |
| String path = pathSupplier.get(); |
| Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(s); |
| while (matcher.find()) { |
| String propertyName = matcher.group(0); |
| |
| if (path.startsWith("activation.file.") && "${project.basedir}".equals(propertyName)) { |
| continue; |
| } |
| addViolation( |
| problems, |
| Severity.WARNING, |
| Version.V30, |
| prefix + path, |
| null, |
| "Failed to interpolate profile activation property " + s + ": " + propertyName |
| + " expressions are not supported during profile activation.", |
| locationSupplier.get()); |
| } |
| } |
| }; |
| Optional<Activation> root = Optional.of(activation); |
| stk.push(new ActivationFrame("activation", root)); |
| root.map(Activation::getFile).ifPresent(fa -> { |
| stk.push(new ActivationFrame("file", Optional.of(fa))); |
| stk.push(new ActivationFrame("exists", Optional.empty())); |
| validator.accept(fa.getExists()); |
| stk.peek().location = "missing"; |
| validator.accept(fa.getMissing()); |
| stk.pop(); |
| stk.pop(); |
| }); |
| root.map(Activation::getOs).ifPresent(oa -> { |
| stk.push(new ActivationFrame("os", Optional.of(oa))); |
| stk.push(new ActivationFrame("arch", Optional.empty())); |
| validator.accept(oa.getArch()); |
| stk.peek().location = "family"; |
| validator.accept(oa.getFamily()); |
| stk.peek().location = "name"; |
| validator.accept(oa.getName()); |
| stk.peek().location = "version"; |
| validator.accept(oa.getVersion()); |
| stk.pop(); |
| stk.pop(); |
| }); |
| root.map(Activation::getProperty).ifPresent(pa -> { |
| stk.push(new ActivationFrame("property", Optional.of(pa))); |
| stk.push(new ActivationFrame("name", Optional.empty())); |
| validator.accept(pa.getName()); |
| stk.peek().location = "value"; |
| validator.accept(pa.getValue()); |
| stk.pop(); |
| stk.pop(); |
| }); |
| root.map(Activation::getJdk).ifPresent(jdk -> { |
| stk.push(new ActivationFrame("jdk", Optional.empty())); |
| validator.accept(jdk); |
| stk.pop(); |
| }); |
| } |
| |
| 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 |
| @SuppressWarnings("checkstyle:MethodLength") |
| 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 " + Objects.toString(dependency.getVersion(), "(?)"); |
| } else { |
| msg = "version " + Objects.toString(existing.getVersion(), "(?)") + " vs " |
| + Objects.toString(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; |
| } |
| |
| Matcher m = CI_FRIENDLY_EXPRESSION.matcher(string.trim()); |
| while (m.find()) { |
| String property = m.group(1); |
| if (!versionProcessor.isValidProperty(property)) { |
| 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.max(firstSegments.length, secondSegments.length); i++) { |
| int result = Long.valueOf(i < firstSegments.length ? firstSegments[i] : "0") |
| .compareTo(Long.valueOf(i < secondSegments.length ? secondSegments[i] : "0")); |
| if (result != 0) { |
| return result; |
| } |
| } |
| return 0; |
| } |
| |
| @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; |
| } |
| } |
| } |