View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.model.validation;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.File;
26  import java.util.Arrays;
27  import java.util.Deque;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Iterator;
31  import java.util.LinkedList;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Objects;
35  import java.util.Optional;
36  import java.util.Set;
37  import java.util.function.Consumer;
38  import java.util.function.Supplier;
39  import java.util.regex.Matcher;
40  import java.util.regex.Pattern;
41  import java.util.stream.Collectors;
42  import java.util.stream.StreamSupport;
43  
44  import org.apache.maven.model.Activation;
45  import org.apache.maven.model.Build;
46  import org.apache.maven.model.BuildBase;
47  import org.apache.maven.model.Dependency;
48  import org.apache.maven.model.DependencyManagement;
49  import org.apache.maven.model.DistributionManagement;
50  import org.apache.maven.model.Exclusion;
51  import org.apache.maven.model.InputLocation;
52  import org.apache.maven.model.InputLocationTracker;
53  import org.apache.maven.model.Model;
54  import org.apache.maven.model.Parent;
55  import org.apache.maven.model.Plugin;
56  import org.apache.maven.model.PluginExecution;
57  import org.apache.maven.model.PluginManagement;
58  import org.apache.maven.model.Profile;
59  import org.apache.maven.model.ReportPlugin;
60  import org.apache.maven.model.Reporting;
61  import org.apache.maven.model.Repository;
62  import org.apache.maven.model.Resource;
63  import org.apache.maven.model.building.ModelBuildingRequest;
64  import org.apache.maven.model.building.ModelProblem.Severity;
65  import org.apache.maven.model.building.ModelProblem.Version;
66  import org.apache.maven.model.building.ModelProblemCollector;
67  import org.apache.maven.model.building.ModelProblemCollectorRequest;
68  import org.apache.maven.model.interpolation.ModelVersionProcessor;
69  import org.codehaus.plexus.util.StringUtils;
70  
71  /**
72   * @deprecated use {@code org.apache.maven.api.services.ModelBuilder} instead
73   */
74  @Named
75  @Singleton
76  @Deprecated(since = "4.0.0")
77  public class DefaultModelValidator implements ModelValidator {
78      public static final String BUILD_ALLOW_EXPRESSION_IN_EFFECTIVE_PROJECT_VERSION =
79              "maven.build.allowExpressionInEffectiveProjectVersion";
80  
81      private static final Pattern CI_FRIENDLY_EXPRESSION = Pattern.compile("\\$\\{(.+?)}");
82      private static final Pattern EXPRESSION_PROJECT_NAME_PATTERN = Pattern.compile("\\$\\{(project.+?)}");
83  
84      private static final String ILLEGAL_FS_CHARS = "\\/:\"<>|?*";
85  
86      private static final String ILLEGAL_VERSION_CHARS = ILLEGAL_FS_CHARS;
87  
88      private static final String ILLEGAL_REPO_ID_CHARS = ILLEGAL_FS_CHARS;
89  
90      private static final String EMPTY = "";
91  
92      private final Set<String> validIds = new HashSet<>();
93  
94      private ModelVersionProcessor versionProcessor;
95  
96      @Inject
97      public DefaultModelValidator(ModelVersionProcessor versionProcessor) {
98          this.versionProcessor = versionProcessor;
99      }
100 
101     @SuppressWarnings("checkstyle:methodlength")
102     @Override
103     public void validateRawModel(Model m, ModelBuildingRequest request, ModelProblemCollector problems) {
104         Parent parent = m.getParent();
105         if (parent != null) {
106             validateStringNotEmpty(
107                     "parent.groupId", problems, Severity.FATAL, Version.BASE, parent.getGroupId(), parent);
108 
109             validateStringNotEmpty(
110                     "parent.artifactId", problems, Severity.FATAL, Version.BASE, parent.getArtifactId(), parent);
111 
112             validateStringNotEmpty(
113                     "parent.version", problems, Severity.FATAL, Version.BASE, parent.getVersion(), parent);
114 
115             if (equals(parent.getGroupId(), m.getGroupId()) && equals(parent.getArtifactId(), m.getArtifactId())) {
116                 addViolation(
117                         problems,
118                         Severity.FATAL,
119                         Version.BASE,
120                         "parent.artifactId",
121                         null,
122                         "must be changed"
123                                 + ", the parent element cannot have the same groupId:artifactId as the project.",
124                         parent);
125             }
126 
127             if (equals("LATEST", parent.getVersion()) || equals("RELEASE", parent.getVersion())) {
128                 addViolation(
129                         problems,
130                         Severity.WARNING,
131                         Version.BASE,
132                         "parent.version",
133                         null,
134                         "is either LATEST or RELEASE (both of them are being deprecated)",
135                         parent);
136             }
137         }
138 
139         if (request.getValidationLevel() == ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL) {
140             // profiles: they are essential for proper model building (may contribute profiles, dependencies...)
141             HashSet<String> minProfileIds = new HashSet<>();
142             for (Profile profile : m.getProfiles()) {
143                 if (!minProfileIds.add(profile.getId())) {
144                     addViolation(
145                             problems,
146                             Severity.WARNING,
147                             Version.BASE,
148                             "profiles.profile.id",
149                             null,
150                             "Duplicate activation for profile " + profile.getId(),
151                             profile);
152                 }
153             }
154         } else if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
155             Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
156 
157             // [MNG-6074] Maven should produce an error if no model version has been set in a POM file used to build an
158             // effective model.
159             //
160             // As of 3.4, the model version is mandatory even in raw models. The XML element still is optional in the
161             // XML schema and this will not change anytime soon. We do not want to build effective models based on
162             // models without a version starting with 3.4.
163             validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.V20, m.getModelVersion(), m);
164 
165             validateModelVersion(problems, m.getModelVersion(), m, "4.0.0");
166 
167             validateStringNoExpression("groupId", problems, Severity.WARNING, Version.V20, m.getGroupId(), m);
168             if (parent == null) {
169                 validateStringNotEmpty("groupId", problems, Severity.FATAL, Version.V20, m.getGroupId(), m);
170             }
171 
172             validateStringNoExpression("artifactId", problems, Severity.WARNING, Version.V20, m.getArtifactId(), m);
173             validateStringNotEmpty("artifactId", problems, Severity.FATAL, Version.V20, m.getArtifactId(), m);
174 
175             validateVersionNoExpression("version", problems, Severity.WARNING, Version.V20, m.getVersion(), m);
176             if (parent == null) {
177                 validateStringNotEmpty("version", problems, Severity.FATAL, Version.V20, m.getVersion(), m);
178             }
179 
180             validate20RawDependencies(problems, m.getDependencies(), "dependencies.dependency.", EMPTY, request);
181 
182             validate20RawDependenciesSelfReferencing(
183                     problems, m, m.getDependencies(), "dependencies.dependency", request);
184 
185             if (m.getDependencyManagement() != null) {
186                 validate20RawDependencies(
187                         problems,
188                         m.getDependencyManagement().getDependencies(),
189                         "dependencyManagement.dependencies.dependency.",
190                         EMPTY,
191                         request);
192             }
193 
194             validateRawRepositories(problems, m.getRepositories(), "repositories.repository.", EMPTY, request);
195 
196             validateRawRepositories(
197                     problems, m.getPluginRepositories(), "pluginRepositories.pluginRepository.", EMPTY, request);
198 
199             Build build = m.getBuild();
200             if (build != null) {
201                 validate20RawPlugins(problems, build.getPlugins(), "build.plugins.plugin.", EMPTY, request);
202 
203                 PluginManagement mgmt = build.getPluginManagement();
204                 if (mgmt != null) {
205                     validate20RawPlugins(
206                             problems, mgmt.getPlugins(), "build.pluginManagement.plugins.plugin.", EMPTY, request);
207                 }
208             }
209 
210             Set<String> profileIds = new HashSet<>();
211 
212             for (Profile profile : m.getProfiles()) {
213                 String prefix = "profiles.profile[" + profile.getId() + "].";
214 
215                 if (!profileIds.add(profile.getId())) {
216                     addViolation(
217                             problems,
218                             errOn30,
219                             Version.V20,
220                             "profiles.profile.id",
221                             null,
222                             "must be unique but found duplicate profile with id " + profile.getId(),
223                             profile);
224                 }
225 
226                 validate30RawProfileActivation(problems, profile.getActivation(), prefix);
227 
228                 validate20RawDependencies(
229                         problems, profile.getDependencies(), prefix, "dependencies.dependency.", request);
230 
231                 if (profile.getDependencyManagement() != null) {
232                     validate20RawDependencies(
233                             problems,
234                             profile.getDependencyManagement().getDependencies(),
235                             prefix,
236                             "dependencyManagement.dependencies.dependency.",
237                             request);
238                 }
239 
240                 validateRawRepositories(
241                         problems, profile.getRepositories(), prefix, "repositories.repository.", request);
242 
243                 validateRawRepositories(
244                         problems,
245                         profile.getPluginRepositories(),
246                         prefix,
247                         "pluginRepositories.pluginRepository.",
248                         request);
249 
250                 BuildBase buildBase = profile.getBuild();
251                 if (buildBase != null) {
252                     validate20RawPlugins(problems, buildBase.getPlugins(), prefix, "plugins.plugin.", request);
253 
254                     PluginManagement mgmt = buildBase.getPluginManagement();
255                     if (mgmt != null) {
256                         validate20RawPlugins(
257                                 problems, mgmt.getPlugins(), prefix, "pluginManagement.plugins.plugin.", request);
258                     }
259                 }
260             }
261         }
262     }
263 
264     private void validate30RawProfileActivation(ModelProblemCollector problems, Activation activation, String prefix) {
265         if (activation == null) {
266             return;
267         }
268         class ActivationFrame {
269             String location;
270             Optional<? extends InputLocationTracker> parent;
271 
272             ActivationFrame(String location, Optional<? extends InputLocationTracker> parent) {
273                 this.location = location;
274                 this.parent = parent;
275             }
276         }
277         final Deque<ActivationFrame> stk = new LinkedList<>();
278 
279         final Supplier<String> pathSupplier = () -> {
280             final boolean parallel = false;
281             return StreamSupport.stream(((Iterable<ActivationFrame>) stk::descendingIterator).spliterator(), parallel)
282                     .map(f -> f.location)
283                     .collect(Collectors.joining("."));
284         };
285         final Supplier<InputLocation> locationSupplier = () -> {
286             if (stk.size() < 2) {
287                 return null;
288             }
289             Iterator<ActivationFrame> f = stk.iterator();
290 
291             String location = f.next().location;
292             ActivationFrame parent = f.next();
293 
294             return parent.parent.map(p -> p.getLocation(location)).orElse(null);
295         };
296         final Consumer<String> validator = s -> {
297             if (hasProjectExpression(s)) {
298                 String path = pathSupplier.get();
299                 Matcher matcher = EXPRESSION_PROJECT_NAME_PATTERN.matcher(s);
300                 while (matcher.find()) {
301                     String propertyName = matcher.group(0);
302 
303                     if (path.startsWith("activation.file.") && "${project.basedir}".equals(propertyName)) {
304                         continue;
305                     }
306                     addViolation(
307                             problems,
308                             Severity.WARNING,
309                             Version.V30,
310                             prefix + path,
311                             null,
312                             "Failed to interpolate profile activation property " + s + ": " + propertyName
313                                     + " expressions are not supported during profile activation.",
314                             locationSupplier.get());
315                 }
316             }
317         };
318         Optional<Activation> root = Optional.of(activation);
319         stk.push(new ActivationFrame("activation", root));
320         root.map(Activation::getFile).ifPresent(fa -> {
321             stk.push(new ActivationFrame("file", Optional.of(fa)));
322             stk.push(new ActivationFrame("exists", Optional.empty()));
323             validator.accept(fa.getExists());
324             stk.peek().location = "missing";
325             validator.accept(fa.getMissing());
326             stk.pop();
327             stk.pop();
328         });
329         root.map(Activation::getOs).ifPresent(oa -> {
330             stk.push(new ActivationFrame("os", Optional.of(oa)));
331             stk.push(new ActivationFrame("arch", Optional.empty()));
332             validator.accept(oa.getArch());
333             stk.peek().location = "family";
334             validator.accept(oa.getFamily());
335             stk.peek().location = "name";
336             validator.accept(oa.getName());
337             stk.peek().location = "version";
338             validator.accept(oa.getVersion());
339             stk.pop();
340             stk.pop();
341         });
342         root.map(Activation::getProperty).ifPresent(pa -> {
343             stk.push(new ActivationFrame("property", Optional.of(pa)));
344             stk.push(new ActivationFrame("name", Optional.empty()));
345             validator.accept(pa.getName());
346             stk.peek().location = "value";
347             validator.accept(pa.getValue());
348             stk.pop();
349             stk.pop();
350         });
351         root.map(Activation::getJdk).ifPresent(jdk -> {
352             stk.push(new ActivationFrame("jdk", Optional.empty()));
353             validator.accept(jdk);
354             stk.pop();
355         });
356     }
357 
358     private void validate20RawPlugins(
359             ModelProblemCollector problems,
360             List<Plugin> plugins,
361             String prefix,
362             String prefix2,
363             ModelBuildingRequest request) {
364         Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);
365 
366         Map<String, Plugin> index = new HashMap<>();
367 
368         for (Plugin plugin : plugins) {
369             if (plugin.getGroupId() == null
370                     || (plugin.getGroupId() != null
371                             && plugin.getGroupId().trim().isEmpty())) {
372                 addViolation(
373                         problems,
374                         Severity.FATAL,
375                         Version.V20,
376                         prefix + prefix2 + "(groupId:artifactId)",
377                         null,
378                         "groupId of a plugin must be defined. ",
379                         plugin);
380             }
381 
382             if (plugin.getArtifactId() == null
383                     || (plugin.getArtifactId() != null
384                             && plugin.getArtifactId().trim().isEmpty())) {
385                 addViolation(
386                         problems,
387                         Severity.FATAL,
388                         Version.V20,
389                         prefix + prefix2 + "(groupId:artifactId)",
390                         null,
391                         "artifactId of a plugin must be defined. ",
392                         plugin);
393             }
394 
395             // This will catch cases like <version></version> or <version/>
396             if (plugin.getVersion() != null && plugin.getVersion().trim().isEmpty()) {
397                 addViolation(
398                         problems,
399                         Severity.FATAL,
400                         Version.V20,
401                         prefix + prefix2 + "(groupId:artifactId)",
402                         null,
403                         "version of a plugin must be defined. ",
404                         plugin);
405             }
406 
407             String key = plugin.getKey();
408 
409             Plugin existing = index.get(key);
410 
411             if (existing != null) {
412                 addViolation(
413                         problems,
414                         errOn31,
415                         Version.V20,
416                         prefix + prefix2 + "(groupId:artifactId)",
417                         null,
418                         "must be unique but found duplicate declaration of plugin " + key,
419                         plugin);
420             } else {
421                 index.put(key, plugin);
422             }
423 
424             Set<String> executionIds = new HashSet<>();
425 
426             for (PluginExecution exec : plugin.getExecutions()) {
427                 if (!executionIds.add(exec.getId())) {
428                     addViolation(
429                             problems,
430                             Severity.ERROR,
431                             Version.V20,
432                             prefix + prefix2 + "[" + plugin.getKey() + "].executions.execution.id",
433                             null,
434                             "must be unique but found duplicate execution with id " + exec.getId(),
435                             exec);
436                 }
437             }
438         }
439     }
440 
441     @Override
442     @SuppressWarnings("checkstyle:MethodLength")
443     public void validateEffectiveModel(Model m, ModelBuildingRequest request, ModelProblemCollector problems) {
444         validateStringNotEmpty("modelVersion", problems, Severity.ERROR, Version.BASE, m.getModelVersion(), m);
445 
446         validateId("groupId", problems, m.getGroupId(), m);
447 
448         validateId("artifactId", problems, m.getArtifactId(), m);
449 
450         validateStringNotEmpty("packaging", problems, Severity.ERROR, Version.BASE, m.getPackaging(), m);
451 
452         if (!m.getModules().isEmpty()) {
453             if (!"pom".equals(m.getPackaging())) {
454                 addViolation(
455                         problems,
456                         Severity.ERROR,
457                         Version.BASE,
458                         "packaging",
459                         null,
460                         "with value '" + m.getPackaging() + "' is invalid. Aggregator projects "
461                                 + "require 'pom' as packaging.",
462                         m);
463             }
464 
465             for (int i = 0, n = m.getModules().size(); i < n; i++) {
466                 String module = m.getModules().get(i);
467                 if (StringUtils.isBlank(module)) {
468                     addViolation(
469                             problems,
470                             Severity.ERROR,
471                             Version.BASE,
472                             "modules.module[" + i + "]",
473                             null,
474                             "has been specified without a path to the project directory.",
475                             m.getLocation("modules"));
476                 }
477             }
478         }
479 
480         validateStringNotEmpty("version", problems, Severity.ERROR, Version.BASE, m.getVersion(), m);
481 
482         Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
483 
484         validateEffectiveDependencies(problems, m, m.getDependencies(), false, request);
485 
486         DependencyManagement mgmt = m.getDependencyManagement();
487         if (mgmt != null) {
488             validateEffectiveDependencies(problems, m, mgmt.getDependencies(), true, request);
489         }
490 
491         if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
492             Set<String> modules = new HashSet<>();
493             for (int i = 0, n = m.getModules().size(); i < n; i++) {
494                 String module = m.getModules().get(i);
495                 if (!modules.add(module)) {
496                     addViolation(
497                             problems,
498                             Severity.ERROR,
499                             Version.V20,
500                             "modules.module[" + i + "]",
501                             null,
502                             "specifies duplicate child module " + module,
503                             m.getLocation("modules"));
504                 }
505             }
506 
507             Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);
508 
509             validateBannedCharacters(
510                     EMPTY, "version", problems, errOn31, Version.V20, m.getVersion(), null, m, ILLEGAL_VERSION_CHARS);
511             validate20ProperSnapshotVersion("version", problems, errOn31, Version.V20, m.getVersion(), null, m);
512             if (hasExpression(m.getVersion())) {
513                 Severity versionExpressionSeverity = Severity.ERROR;
514                 if (Boolean.parseBoolean(
515                         m.getProperties().getProperty(BUILD_ALLOW_EXPRESSION_IN_EFFECTIVE_PROJECT_VERSION))) {
516                     versionExpressionSeverity = Severity.WARNING;
517                 }
518                 addViolation(
519                         problems,
520                         versionExpressionSeverity,
521                         Version.V20,
522                         "version",
523                         null,
524                         "must be a constant version but is '" + m.getVersion() + "'.",
525                         m);
526             }
527 
528             Build build = m.getBuild();
529             if (build != null) {
530                 for (Plugin p : build.getPlugins()) {
531                     validateStringNotEmpty(
532                             "build.plugins.plugin.artifactId",
533                             problems,
534                             Severity.ERROR,
535                             Version.V20,
536                             p.getArtifactId(),
537                             p);
538 
539                     validateStringNotEmpty(
540                             "build.plugins.plugin.groupId", problems, Severity.ERROR, Version.V20, p.getGroupId(), p);
541 
542                     validate20PluginVersion(
543                             "build.plugins.plugin.version", problems, p.getVersion(), p.getKey(), p, request);
544 
545                     validateBoolean(
546                             "build.plugins.plugin.inherited",
547                             EMPTY,
548                             problems,
549                             errOn30,
550                             Version.V20,
551                             p.getInherited(),
552                             p.getKey(),
553                             p);
554 
555                     validateBoolean(
556                             "build.plugins.plugin.extensions",
557                             EMPTY,
558                             problems,
559                             errOn30,
560                             Version.V20,
561                             p.getExtensions(),
562                             p.getKey(),
563                             p);
564 
565                     validate20EffectivePluginDependencies(problems, p, request);
566                 }
567 
568                 validate20RawResources(problems, build.getResources(), "build.resources.resource.", request);
569 
570                 validate20RawResources(
571                         problems, build.getTestResources(), "build.testResources.testResource.", request);
572             }
573 
574             Reporting reporting = m.getReporting();
575             if (reporting != null) {
576                 for (ReportPlugin p : reporting.getPlugins()) {
577                     validateStringNotEmpty(
578                             "reporting.plugins.plugin.artifactId",
579                             problems,
580                             Severity.ERROR,
581                             Version.V20,
582                             p.getArtifactId(),
583                             p);
584 
585                     validateStringNotEmpty(
586                             "reporting.plugins.plugin.groupId",
587                             problems,
588                             Severity.ERROR,
589                             Version.V20,
590                             p.getGroupId(),
591                             p);
592                 }
593             }
594 
595             for (Repository repository : m.getRepositories()) {
596                 validate20EffectiveRepository(problems, repository, "repositories.repository.", request);
597             }
598 
599             for (Repository repository : m.getPluginRepositories()) {
600                 validate20EffectiveRepository(problems, repository, "pluginRepositories.pluginRepository.", request);
601             }
602 
603             DistributionManagement distMgmt = m.getDistributionManagement();
604             if (distMgmt != null) {
605                 if (distMgmt.getStatus() != null) {
606                     addViolation(
607                             problems,
608                             Severity.ERROR,
609                             Version.V20,
610                             "distributionManagement.status",
611                             null,
612                             "must not be specified.",
613                             distMgmt);
614                 }
615 
616                 validate20EffectiveRepository(
617                         problems, distMgmt.getRepository(), "distributionManagement.repository.", request);
618                 validate20EffectiveRepository(
619                         problems,
620                         distMgmt.getSnapshotRepository(),
621                         "distributionManagement.snapshotRepository.",
622                         request);
623             }
624         }
625     }
626 
627     private void validate20RawDependencies(
628             ModelProblemCollector problems,
629             List<Dependency> dependencies,
630             String prefix,
631             String prefix2,
632             ModelBuildingRequest request) {
633         Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
634         Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);
635 
636         Map<String, Dependency> index = new HashMap<>();
637 
638         for (Dependency dependency : dependencies) {
639             String key = dependency.getManagementKey();
640 
641             if ("import".equals(dependency.getScope())) {
642                 if (!"pom".equals(dependency.getType())) {
643                     addViolation(
644                             problems,
645                             Severity.WARNING,
646                             Version.V20,
647                             prefix + prefix2 + "type",
648                             key,
649                             "must be 'pom' to import the managed dependencies.",
650                             dependency);
651                 } else if (StringUtils.isNotEmpty(dependency.getClassifier())) {
652                     addViolation(
653                             problems,
654                             errOn30,
655                             Version.V20,
656                             prefix + prefix2 + "classifier",
657                             key,
658                             "must be empty, imported POM cannot have a classifier.",
659                             dependency);
660                 }
661             } else if ("system".equals(dependency.getScope())) {
662 
663                 if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1) {
664                     addViolation(
665                             problems,
666                             Severity.WARNING,
667                             Version.V31,
668                             prefix + prefix2 + "scope",
669                             key,
670                             "declares usage of deprecated 'system' scope ",
671                             dependency);
672                 }
673 
674                 String sysPath = dependency.getSystemPath();
675                 if (StringUtils.isNotEmpty(sysPath)) {
676                     if (!hasExpression(sysPath)) {
677                         addViolation(
678                                 problems,
679                                 Severity.WARNING,
680                                 Version.V20,
681                                 prefix + prefix2 + "systemPath",
682                                 key,
683                                 "should use a variable instead of a hard-coded path " + sysPath,
684                                 dependency);
685                     } else if (sysPath.contains("${basedir}") || sysPath.contains("${project.basedir}")) {
686                         addViolation(
687                                 problems,
688                                 Severity.WARNING,
689                                 Version.V20,
690                                 prefix + prefix2 + "systemPath",
691                                 key,
692                                 "should not point at files within the project directory, " + sysPath
693                                         + " will be unresolvable by dependent projects",
694                                 dependency);
695                     }
696                 }
697             }
698 
699             if (equals("LATEST", dependency.getVersion()) || equals("RELEASE", dependency.getVersion())) {
700                 addViolation(
701                         problems,
702                         Severity.WARNING,
703                         Version.BASE,
704                         prefix + prefix2 + "version",
705                         key,
706                         "is either LATEST or RELEASE (both of them are being deprecated)",
707                         dependency);
708             }
709 
710             Dependency existing = index.get(key);
711 
712             if (existing != null) {
713                 String msg;
714                 if (equals(existing.getVersion(), dependency.getVersion())) {
715                     msg = "duplicate declaration of version " + Objects.toString(dependency.getVersion(), "(?)");
716                 } else {
717                     msg = "version " + Objects.toString(existing.getVersion(), "(?)") + " vs "
718                             + Objects.toString(dependency.getVersion(), "(?)");
719                 }
720 
721                 addViolation(
722                         problems,
723                         errOn31,
724                         Version.V20,
725                         prefix + prefix2 + "(groupId:artifactId:type:classifier)",
726                         null,
727                         "must be unique: " + key + " -> " + msg,
728                         dependency);
729             } else {
730                 index.put(key, dependency);
731             }
732         }
733     }
734 
735     private void validate20RawDependenciesSelfReferencing(
736             ModelProblemCollector problems,
737             Model m,
738             List<Dependency> dependencies,
739             String prefix,
740             ModelBuildingRequest request) {
741         // We only check for groupId/artifactId/version/classifier cause if there is another
742         // module with the same groupId/artifactId/version/classifier this will fail the build
743         // earlier like "Project '...' is duplicated in the reactor.
744         // So it is sufficient to check only groupId/artifactId/version/classifier and not the
745         // packaging type.
746         for (Dependency dependency : dependencies) {
747             String key = dependency.getGroupId() + ":" + dependency.getArtifactId() + ":" + dependency.getVersion()
748                     + (dependency.getClassifier() != null ? ":" + dependency.getClassifier() : EMPTY);
749             String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
750             if (key.equals(mKey)) {
751                 // This means a module which is build has a dependency which has the same
752                 // groupId, artifactId, version and classifier coordinates. This is in consequence
753                 // a self reference or in other words a circular reference which can not being resolved.
754                 addViolation(
755                         problems,
756                         Severity.FATAL,
757                         Version.V31,
758                         prefix + "[" + key + "]",
759                         key,
760                         "is referencing itself.",
761                         dependency);
762             }
763         }
764     }
765 
766     private void validateEffectiveDependencies(
767             ModelProblemCollector problems,
768             Model m,
769             List<Dependency> dependencies,
770             boolean management,
771             ModelBuildingRequest request) {
772         Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
773 
774         String prefix = management ? "dependencyManagement.dependencies.dependency." : "dependencies.dependency.";
775 
776         for (Dependency d : dependencies) {
777             validateEffectiveDependency(problems, d, management, prefix, request);
778 
779             if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
780                 validateBoolean(
781                         prefix, "optional", problems, errOn30, Version.V20, d.getOptional(), d.getManagementKey(), d);
782 
783                 if (!management) {
784                     validateVersion(
785                             prefix, "version", problems, errOn30, Version.V20, d.getVersion(), d.getManagementKey(), d);
786 
787                     /*
788                      * TODO Extensions like Flex Mojos use custom scopes like "merged", "internal", "external", etc. In
789                      * order to don't break backward-compat with those, only warn but don't error out.
790                      */
791                     validateEnum(
792                             prefix,
793                             "scope",
794                             problems,
795                             Severity.WARNING,
796                             Version.V20,
797                             d.getScope(),
798                             d.getManagementKey(),
799                             d,
800                             "provided",
801                             "compile",
802                             "runtime",
803                             "test",
804                             "system");
805 
806                     validateEffectiveModelAgainstDependency(prefix, problems, m, d, request);
807                 } else {
808                     validateEnum(
809                             prefix,
810                             "scope",
811                             problems,
812                             Severity.WARNING,
813                             Version.V20,
814                             d.getScope(),
815                             d.getManagementKey(),
816                             d,
817                             "provided",
818                             "compile",
819                             "runtime",
820                             "test",
821                             "system",
822                             "import");
823                 }
824             }
825         }
826     }
827 
828     private void validateEffectiveModelAgainstDependency(
829             String prefix, ModelProblemCollector problems, Model m, Dependency d, ModelBuildingRequest request) {
830         String key = d.getGroupId() + ":" + d.getArtifactId() + ":" + d.getVersion()
831                 + (d.getClassifier() != null ? ":" + d.getClassifier() : EMPTY);
832         String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
833         if (key.equals(mKey)) {
834             // This means a module which is build has a dependency which has the same
835             // groupId, artifactId, version and classifier coordinates. This is in consequence
836             // a self reference or in other words a circular reference which can not being resolved.
837             addViolation(
838                     problems, Severity.FATAL, Version.V31, prefix + "[" + key + "]", key, "is referencing itself.", d);
839         }
840     }
841 
842     private void validate20EffectivePluginDependencies(
843             ModelProblemCollector problems, Plugin plugin, ModelBuildingRequest request) {
844         List<Dependency> dependencies = plugin.getDependencies();
845 
846         if (!dependencies.isEmpty()) {
847             String prefix = "build.plugins.plugin[" + plugin.getKey() + "].dependencies.dependency.";
848 
849             Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
850 
851             for (Dependency d : dependencies) {
852                 validateEffectiveDependency(problems, d, false, prefix, request);
853 
854                 validateVersion(
855                         prefix, "version", problems, errOn30, Version.BASE, d.getVersion(), d.getManagementKey(), d);
856 
857                 validateEnum(
858                         prefix,
859                         "scope",
860                         problems,
861                         errOn30,
862                         Version.BASE,
863                         d.getScope(),
864                         d.getManagementKey(),
865                         d,
866                         "compile",
867                         "runtime",
868                         "system");
869             }
870         }
871     }
872 
873     private void validateEffectiveDependency(
874             ModelProblemCollector problems,
875             Dependency d,
876             boolean management,
877             String prefix,
878             ModelBuildingRequest request) {
879         validateId(
880                 prefix,
881                 "artifactId",
882                 problems,
883                 Severity.ERROR,
884                 Version.BASE,
885                 d.getArtifactId(),
886                 d.getManagementKey(),
887                 d);
888 
889         validateId(prefix, "groupId", problems, Severity.ERROR, Version.BASE, d.getGroupId(), d.getManagementKey(), d);
890 
891         if (!management) {
892             validateStringNotEmpty(
893                     prefix, "type", problems, Severity.ERROR, Version.BASE, d.getType(), d.getManagementKey(), d);
894 
895             validateDependencyVersion(problems, d, prefix);
896         }
897 
898         if ("system".equals(d.getScope())) {
899             String systemPath = d.getSystemPath();
900 
901             if (StringUtils.isEmpty(systemPath)) {
902                 addViolation(
903                         problems,
904                         Severity.ERROR,
905                         Version.BASE,
906                         prefix + "systemPath",
907                         d.getManagementKey(),
908                         "is missing.",
909                         d);
910             } else {
911                 File sysFile = new File(systemPath);
912                 if (!sysFile.isAbsolute()) {
913                     addViolation(
914                             problems,
915                             Severity.ERROR,
916                             Version.BASE,
917                             prefix + "systemPath",
918                             d.getManagementKey(),
919                             "must specify an absolute path but is " + systemPath,
920                             d);
921                 } else if (!sysFile.isFile()) {
922                     String msg = "refers to a non-existing file " + sysFile.getAbsolutePath();
923                     systemPath = systemPath.replace('/', File.separatorChar).replace('\\', File.separatorChar);
924                     String jdkHome =
925                             request.getSystemProperties().getProperty("java.home", EMPTY) + File.separator + "..";
926                     if (systemPath.startsWith(jdkHome)) {
927                         msg += ". Please verify that you run Maven using a JDK and not just a JRE.";
928                     }
929                     addViolation(
930                             problems,
931                             Severity.WARNING,
932                             Version.BASE,
933                             prefix + "systemPath",
934                             d.getManagementKey(),
935                             msg,
936                             d);
937                 }
938             }
939         } else if (StringUtils.isNotEmpty(d.getSystemPath())) {
940             addViolation(
941                     problems,
942                     Severity.ERROR,
943                     Version.BASE,
944                     prefix + "systemPath",
945                     d.getManagementKey(),
946                     "must be omitted." + " This field may only be specified for a dependency with system scope.",
947                     d);
948         }
949 
950         if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
951             for (Exclusion exclusion : d.getExclusions()) {
952                 if (request.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0) {
953                     validateId(
954                             prefix,
955                             "exclusions.exclusion.groupId",
956                             problems,
957                             Severity.WARNING,
958                             Version.V20,
959                             exclusion.getGroupId(),
960                             d.getManagementKey(),
961                             exclusion);
962 
963                     validateId(
964                             prefix,
965                             "exclusions.exclusion.artifactId",
966                             problems,
967                             Severity.WARNING,
968                             Version.V20,
969                             exclusion.getArtifactId(),
970                             d.getManagementKey(),
971                             exclusion);
972                 } else {
973                     validateIdWithWildcards(
974                             prefix,
975                             "exclusions.exclusion.groupId",
976                             problems,
977                             Severity.WARNING,
978                             Version.V30,
979                             exclusion.getGroupId(),
980                             d.getManagementKey(),
981                             exclusion);
982 
983                     validateIdWithWildcards(
984                             prefix,
985                             "exclusions.exclusion.artifactId",
986                             problems,
987                             Severity.WARNING,
988                             Version.V30,
989                             exclusion.getArtifactId(),
990                             d.getManagementKey(),
991                             exclusion);
992                 }
993             }
994         }
995     }
996 
997     /**
998      * @since 3.2.4
999      */
1000     protected void validateDependencyVersion(ModelProblemCollector problems, Dependency d, String prefix) {
1001         validateStringNotEmpty(
1002                 prefix, "version", problems, Severity.ERROR, Version.BASE, d.getVersion(), d.getManagementKey(), d);
1003     }
1004 
1005     private void validateRawRepositories(
1006             ModelProblemCollector problems,
1007             List<Repository> repositories,
1008             String prefix,
1009             String prefix2,
1010             ModelBuildingRequest request) {
1011         Map<String, Repository> index = new HashMap<>();
1012 
1013         for (Repository repository : repositories) {
1014             validateStringNotEmpty(
1015                     prefix, prefix2, "id", problems, Severity.ERROR, Version.V20, repository.getId(), null, repository);
1016 
1017             validateStringNotEmpty(
1018                     prefix,
1019                     prefix2,
1020                     "[" + repository.getId() + "].url",
1021                     problems,
1022                     Severity.ERROR,
1023                     Version.V20,
1024                     repository.getUrl(),
1025                     null,
1026                     repository);
1027 
1028             String key = repository.getId();
1029 
1030             Repository existing = index.get(key);
1031 
1032             if (existing != null) {
1033                 Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
1034 
1035                 addViolation(
1036                         problems,
1037                         errOn30,
1038                         Version.V20,
1039                         prefix + prefix2 + "id",
1040                         null,
1041                         "must be unique: " + repository.getId() + " -> " + existing.getUrl() + " vs "
1042                                 + repository.getUrl(),
1043                         repository);
1044             } else {
1045                 index.put(key, repository);
1046             }
1047         }
1048     }
1049 
1050     private void validate20EffectiveRepository(
1051             ModelProblemCollector problems, Repository repository, String prefix, ModelBuildingRequest request) {
1052         if (repository != null) {
1053             Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1);
1054 
1055             validateBannedCharacters(
1056                     prefix,
1057                     "id",
1058                     problems,
1059                     errOn31,
1060                     Version.V20,
1061                     repository.getId(),
1062                     null,
1063                     repository,
1064                     ILLEGAL_REPO_ID_CHARS);
1065 
1066             if ("local".equals(repository.getId())) {
1067                 addViolation(
1068                         problems,
1069                         errOn31,
1070                         Version.V20,
1071                         prefix + "id",
1072                         null,
1073                         "must not be 'local'" + ", this identifier is reserved for the local repository"
1074                                 + ", using it for other repositories will corrupt your repository metadata.",
1075                         repository);
1076             }
1077 
1078             if ("legacy".equals(repository.getLayout())) {
1079                 addViolation(
1080                         problems,
1081                         Severity.WARNING,
1082                         Version.V20,
1083                         prefix + "layout",
1084                         repository.getId(),
1085                         "uses the unsupported value 'legacy', artifact resolution might fail.",
1086                         repository);
1087             }
1088         }
1089     }
1090 
1091     private void validate20RawResources(
1092             ModelProblemCollector problems, List<Resource> resources, String prefix, ModelBuildingRequest request) {
1093         Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
1094 
1095         for (Resource resource : resources) {
1096             validateStringNotEmpty(
1097                     prefix,
1098                     "directory",
1099                     problems,
1100                     Severity.ERROR,
1101                     Version.V20,
1102                     resource.getDirectory(),
1103                     null,
1104                     resource);
1105 
1106             validateBoolean(
1107                     prefix,
1108                     "filtering",
1109                     problems,
1110                     errOn30,
1111                     Version.V20,
1112                     resource.getFiltering(),
1113                     resource.getDirectory(),
1114                     resource);
1115         }
1116     }
1117 
1118     // ----------------------------------------------------------------------
1119     // Field validation
1120     // ----------------------------------------------------------------------
1121 
1122     private boolean validateId(
1123             String fieldName, ModelProblemCollector problems, String id, InputLocationTracker tracker) {
1124         return validateId(EMPTY, fieldName, problems, Severity.ERROR, Version.BASE, id, null, tracker);
1125     }
1126 
1127     @SuppressWarnings("checkstyle:parameternumber")
1128     private boolean validateId(
1129             String prefix,
1130             String fieldName,
1131             ModelProblemCollector problems,
1132             Severity severity,
1133             Version version,
1134             String id,
1135             String sourceHint,
1136             InputLocationTracker tracker) {
1137         if (validIds.contains(id)) {
1138             return true;
1139         }
1140         if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
1141             return false;
1142         } else {
1143             if (!isValidId(id)) {
1144                 addViolation(
1145                         problems,
1146                         severity,
1147                         version,
1148                         prefix + fieldName,
1149                         sourceHint,
1150                         "with value '" + id + "' does not match a valid id pattern.",
1151                         tracker);
1152                 return false;
1153             }
1154             validIds.add(id);
1155             return true;
1156         }
1157     }
1158 
1159     private boolean isValidId(String id) {
1160         for (int i = 0; i < id.length(); i++) {
1161             char c = id.charAt(i);
1162             if (!isValidIdCharacter(c)) {
1163                 return false;
1164             }
1165         }
1166         return true;
1167     }
1168 
1169     private boolean isValidIdCharacter(char c) {
1170         return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.';
1171     }
1172 
1173     @SuppressWarnings("checkstyle:parameternumber")
1174     private boolean validateIdWithWildcards(
1175             String prefix,
1176             String fieldName,
1177             ModelProblemCollector problems,
1178             Severity severity,
1179             Version version,
1180             String id,
1181             String sourceHint,
1182             InputLocationTracker tracker) {
1183         if (!validateStringNotEmpty(prefix, fieldName, problems, severity, version, id, sourceHint, tracker)) {
1184             return false;
1185         } else {
1186             if (!isValidIdWithWildCards(id)) {
1187                 addViolation(
1188                         problems,
1189                         severity,
1190                         version,
1191                         prefix + fieldName,
1192                         sourceHint,
1193                         "with value '" + id + "' does not match a valid id pattern.",
1194                         tracker);
1195                 return false;
1196             }
1197             return true;
1198         }
1199     }
1200 
1201     private boolean isValidIdWithWildCards(String id) {
1202         for (int i = 0; i < id.length(); i++) {
1203             char c = id.charAt(i);
1204             if (!isValidIdWithWildCardCharacter(c)) {
1205                 return false;
1206             }
1207         }
1208         return true;
1209     }
1210 
1211     private boolean isValidIdWithWildCardCharacter(char c) {
1212         return isValidIdCharacter(c) || c == '?' || c == '*';
1213     }
1214 
1215     private boolean validateStringNoExpression(
1216             String fieldName,
1217             ModelProblemCollector problems,
1218             Severity severity,
1219             Version version,
1220             String string,
1221             InputLocationTracker tracker) {
1222         if (!hasExpression(string)) {
1223             return true;
1224         }
1225 
1226         addViolation(
1227                 problems,
1228                 severity,
1229                 version,
1230                 fieldName,
1231                 null,
1232                 "contains an expression but should be a constant.",
1233                 tracker);
1234 
1235         return false;
1236     }
1237 
1238     private boolean validateVersionNoExpression(
1239             String fieldName,
1240             ModelProblemCollector problems,
1241             Severity severity,
1242             Version version,
1243             String string,
1244             InputLocationTracker tracker) {
1245         if (!hasExpression(string)) {
1246             return true;
1247         }
1248 
1249         Matcher m = CI_FRIENDLY_EXPRESSION.matcher(string.trim());
1250         while (m.find()) {
1251             String property = m.group(1);
1252             if (!versionProcessor.isValidProperty(property)) {
1253                 addViolation(
1254                         problems,
1255                         severity,
1256                         version,
1257                         fieldName,
1258                         null,
1259                         "contains an expression but should be a constant.",
1260                         tracker);
1261                 return false;
1262             }
1263         }
1264 
1265         return true;
1266     }
1267 
1268     private boolean hasExpression(String value) {
1269         return value != null && value.contains("${");
1270     }
1271 
1272     private boolean hasProjectExpression(String value) {
1273         return value != null && value.contains("${project.");
1274     }
1275 
1276     private boolean validateStringNotEmpty(
1277             String fieldName,
1278             ModelProblemCollector problems,
1279             Severity severity,
1280             Version version,
1281             String string,
1282             InputLocationTracker tracker) {
1283         return validateStringNotEmpty(EMPTY, fieldName, problems, severity, version, string, null, tracker);
1284     }
1285 
1286     /**
1287      * Asserts:
1288      * <p/>
1289      * <ul>
1290      * <li><code>string != null</code>
1291      * <li><code>string.length > 0</code>
1292      * </ul>
1293      */
1294     @SuppressWarnings("checkstyle:parameternumber")
1295     private boolean validateStringNotEmpty(
1296             String prefix,
1297             String prefix2,
1298             String fieldName,
1299             ModelProblemCollector problems,
1300             Severity severity,
1301             Version version,
1302             String string,
1303             String sourceHint,
1304             InputLocationTracker tracker) {
1305         if (!validateNotNull(prefix, prefix2, fieldName, problems, severity, version, string, sourceHint, tracker)) {
1306             return false;
1307         }
1308 
1309         if (!string.isEmpty()) {
1310             return true;
1311         }
1312 
1313         addViolation(problems, severity, version, prefix + prefix2 + fieldName, sourceHint, "is missing.", tracker);
1314 
1315         return false;
1316     }
1317 
1318     /**
1319      * Asserts:
1320      * <p/>
1321      * <ul>
1322      * <li><code>string != null</code>
1323      * <li><code>string.length > 0</code>
1324      * </ul>
1325      */
1326     @SuppressWarnings("checkstyle:parameternumber")
1327     private boolean validateStringNotEmpty(
1328             String prefix,
1329             String fieldName,
1330             ModelProblemCollector problems,
1331             Severity severity,
1332             Version version,
1333             String string,
1334             String sourceHint,
1335             InputLocationTracker tracker) {
1336         if (!validateNotNull(prefix, fieldName, problems, severity, version, string, sourceHint, tracker)) {
1337             return false;
1338         }
1339 
1340         if (string.length() > 0) {
1341             return true;
1342         }
1343 
1344         addViolation(problems, severity, version, prefix + fieldName, sourceHint, "is missing.", tracker);
1345 
1346         return false;
1347     }
1348 
1349     /**
1350      * Asserts:
1351      * <p/>
1352      * <ul>
1353      * <li><code>string != null</code>
1354      * </ul>
1355      */
1356     @SuppressWarnings("checkstyle:parameternumber")
1357     private boolean validateNotNull(
1358             String prefix,
1359             String fieldName,
1360             ModelProblemCollector problems,
1361             Severity severity,
1362             Version version,
1363             Object object,
1364             String sourceHint,
1365             InputLocationTracker tracker) {
1366         if (object != null) {
1367             return true;
1368         }
1369 
1370         addViolation(problems, severity, version, prefix + fieldName, sourceHint, "is missing.", tracker);
1371 
1372         return false;
1373     }
1374 
1375     /**
1376      * Asserts:
1377      * <p/>
1378      * <ul>
1379      * <li><code>string != null</code>
1380      * </ul>
1381      */
1382     @SuppressWarnings("checkstyle:parameternumber")
1383     private boolean validateNotNull(
1384             String prefix,
1385             String prefix2,
1386             String fieldName,
1387             ModelProblemCollector problems,
1388             Severity severity,
1389             Version version,
1390             Object object,
1391             String sourceHint,
1392             InputLocationTracker tracker) {
1393         if (object != null) {
1394             return true;
1395         }
1396 
1397         addViolation(problems, severity, version, prefix + prefix2 + fieldName, sourceHint, "is missing.", tracker);
1398 
1399         return false;
1400     }
1401 
1402     @SuppressWarnings("checkstyle:parameternumber")
1403     private boolean validateBoolean(
1404             String prefix,
1405             String fieldName,
1406             ModelProblemCollector problems,
1407             Severity severity,
1408             Version version,
1409             String string,
1410             String sourceHint,
1411             InputLocationTracker tracker) {
1412         if (string == null || string.length() <= 0) {
1413             return true;
1414         }
1415 
1416         if ("true".equalsIgnoreCase(string) || "false".equalsIgnoreCase(string)) {
1417             return true;
1418         }
1419 
1420         addViolation(
1421                 problems,
1422                 severity,
1423                 version,
1424                 prefix + fieldName,
1425                 sourceHint,
1426                 "must be 'true' or 'false' but is '" + string + "'.",
1427                 tracker);
1428 
1429         return false;
1430     }
1431 
1432     @SuppressWarnings("checkstyle:parameternumber")
1433     private boolean validateEnum(
1434             String prefix,
1435             String fieldName,
1436             ModelProblemCollector problems,
1437             Severity severity,
1438             Version version,
1439             String string,
1440             String sourceHint,
1441             InputLocationTracker tracker,
1442             String... validValues) {
1443         if (string == null || string.length() <= 0) {
1444             return true;
1445         }
1446 
1447         List<String> values = Arrays.asList(validValues);
1448 
1449         if (values.contains(string)) {
1450             return true;
1451         }
1452 
1453         addViolation(
1454                 problems,
1455                 severity,
1456                 version,
1457                 prefix + fieldName,
1458                 sourceHint,
1459                 "must be one of " + values + " but is '" + string + "'.",
1460                 tracker);
1461 
1462         return false;
1463     }
1464 
1465     @SuppressWarnings("checkstyle:parameternumber")
1466     private boolean validateModelVersion(
1467             ModelProblemCollector problems, String string, InputLocationTracker tracker, String... validVersions) {
1468         if (string == null || string.length() <= 0) {
1469             return true;
1470         }
1471 
1472         List<String> values = Arrays.asList(validVersions);
1473 
1474         if (values.contains(string)) {
1475             return true;
1476         }
1477 
1478         boolean newerThanAll = true;
1479         boolean olderThanAll = true;
1480         for (String validValue : validVersions) {
1481             final int comparison = compareModelVersions(validValue, string);
1482             newerThanAll = newerThanAll && comparison < 0;
1483             olderThanAll = olderThanAll && comparison > 0;
1484         }
1485 
1486         if (newerThanAll) {
1487             addViolation(
1488                     problems,
1489                     Severity.FATAL,
1490                     Version.V20,
1491                     "modelVersion",
1492                     null,
1493                     "of '" + string + "' is newer than the versions supported by this version of Maven: " + values
1494                             + ". Building this project requires a newer version of Maven.",
1495                     tracker);
1496 
1497         } else if (olderThanAll) {
1498             // note this will not be hit for Maven 1.x project.xml as it is an incompatible schema
1499             addViolation(
1500                     problems,
1501                     Severity.FATAL,
1502                     Version.V20,
1503                     "modelVersion",
1504                     null,
1505                     "of '" + string + "' is older than the versions supported by this version of Maven: " + values
1506                             + ". Building this project requires an older version of Maven.",
1507                     tracker);
1508 
1509         } else {
1510             addViolation(
1511                     problems,
1512                     Severity.ERROR,
1513                     Version.V20,
1514                     "modelVersion",
1515                     null,
1516                     "must be one of " + values + " but is '" + string + "'.",
1517                     tracker);
1518         }
1519 
1520         return false;
1521     }
1522 
1523     /**
1524      * Compares two model versions.
1525      *
1526      * @param first the first version.
1527      * @param second the second version.
1528      * @return negative if the first version is newer than the second version, zero if they are the same or positive if
1529      * the second version is the newer.
1530      */
1531     private static int compareModelVersions(String first, String second) {
1532         // we use a dedicated comparator because we control our model version scheme.
1533         String[] firstSegments = StringUtils.split(first, ".");
1534         String[] secondSegments = StringUtils.split(second, ".");
1535         for (int i = 0; i < Math.max(firstSegments.length, secondSegments.length); i++) {
1536             int result = Long.valueOf(i < firstSegments.length ? firstSegments[i] : "0")
1537                     .compareTo(Long.valueOf(i < secondSegments.length ? secondSegments[i] : "0"));
1538             if (result != 0) {
1539                 return result;
1540             }
1541         }
1542         return 0;
1543     }
1544 
1545     @SuppressWarnings("checkstyle:parameternumber")
1546     private boolean validateBannedCharacters(
1547             String prefix,
1548             String fieldName,
1549             ModelProblemCollector problems,
1550             Severity severity,
1551             Version version,
1552             String string,
1553             String sourceHint,
1554             InputLocationTracker tracker,
1555             String banned) {
1556         if (string != null) {
1557             for (int i = string.length() - 1; i >= 0; i--) {
1558                 if (banned.indexOf(string.charAt(i)) >= 0) {
1559                     addViolation(
1560                             problems,
1561                             severity,
1562                             version,
1563                             prefix + fieldName,
1564                             sourceHint,
1565                             "must not contain any of these characters " + banned + " but found " + string.charAt(i),
1566                             tracker);
1567                     return false;
1568                 }
1569             }
1570         }
1571 
1572         return true;
1573     }
1574 
1575     @SuppressWarnings("checkstyle:parameternumber")
1576     private boolean validateVersion(
1577             String prefix,
1578             String fieldName,
1579             ModelProblemCollector problems,
1580             Severity severity,
1581             Version version,
1582             String string,
1583             String sourceHint,
1584             InputLocationTracker tracker) {
1585         if (string == null || string.length() <= 0) {
1586             return true;
1587         }
1588 
1589         if (hasExpression(string)) {
1590             addViolation(
1591                     problems,
1592                     severity,
1593                     version,
1594                     prefix + fieldName,
1595                     sourceHint,
1596                     "must be a valid version but is '" + string + "'.",
1597                     tracker);
1598             return false;
1599         }
1600 
1601         return validateBannedCharacters(
1602                 prefix, fieldName, problems, severity, version, string, sourceHint, tracker, ILLEGAL_VERSION_CHARS);
1603     }
1604 
1605     private boolean validate20ProperSnapshotVersion(
1606             String fieldName,
1607             ModelProblemCollector problems,
1608             Severity severity,
1609             Version version,
1610             String string,
1611             String sourceHint,
1612             InputLocationTracker tracker) {
1613         if (string == null || string.length() <= 0) {
1614             return true;
1615         }
1616 
1617         if (string.endsWith("SNAPSHOT") && !string.endsWith("-SNAPSHOT")) {
1618             addViolation(
1619                     problems,
1620                     severity,
1621                     version,
1622                     fieldName,
1623                     sourceHint,
1624                     "uses an unsupported snapshot version format, should be '*-SNAPSHOT' instead.",
1625                     tracker);
1626             return false;
1627         }
1628 
1629         return true;
1630     }
1631 
1632     private boolean validate20PluginVersion(
1633             String fieldName,
1634             ModelProblemCollector problems,
1635             String string,
1636             String sourceHint,
1637             InputLocationTracker tracker,
1638             ModelBuildingRequest request) {
1639         if (string == null) {
1640             // NOTE: The check for missing plugin versions is handled directly by the model builder
1641             return true;
1642         }
1643 
1644         Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0);
1645 
1646         if (!validateVersion(EMPTY, fieldName, problems, errOn30, Version.V20, string, sourceHint, tracker)) {
1647             return false;
1648         }
1649 
1650         if (string.length() <= 0 || "RELEASE".equals(string) || "LATEST".equals(string)) {
1651             addViolation(
1652                     problems,
1653                     errOn30,
1654                     Version.V20,
1655                     fieldName,
1656                     sourceHint,
1657                     "must be a valid version but is '" + string + "'.",
1658                     tracker);
1659             return false;
1660         }
1661 
1662         return true;
1663     }
1664 
1665     private static void addViolation(
1666             ModelProblemCollector problems,
1667             Severity severity,
1668             Version version,
1669             String fieldName,
1670             String sourceHint,
1671             String message,
1672             InputLocationTracker tracker) {
1673         StringBuilder buffer = new StringBuilder(256);
1674         buffer.append('\'').append(fieldName).append('\'');
1675 
1676         if (sourceHint != null) {
1677             buffer.append(" for ").append(sourceHint);
1678         }
1679 
1680         buffer.append(' ').append(message);
1681 
1682         // CHECKSTYLE_OFF: LineLength
1683         problems.add(new ModelProblemCollectorRequest(severity, version)
1684                 .setMessage(buffer.toString())
1685                 .setLocation(getLocation(fieldName, tracker)));
1686         // CHECKSTYLE_ON: LineLength
1687     }
1688 
1689     private static InputLocation getLocation(String fieldName, InputLocationTracker tracker) {
1690         InputLocation location = null;
1691 
1692         if (tracker != null) {
1693             if (fieldName != null) {
1694                 Object key = fieldName;
1695 
1696                 int idx = fieldName.lastIndexOf('.');
1697                 if (idx >= 0) {
1698                     fieldName = fieldName.substring(idx + 1);
1699                     key = fieldName;
1700                 }
1701 
1702                 if (fieldName.endsWith("]")) {
1703                     key = fieldName.substring(fieldName.lastIndexOf('[') + 1, fieldName.length() - 1);
1704                     try {
1705                         key = Integer.valueOf(key.toString());
1706                     } catch (NumberFormatException e) {
1707                         // use key as is
1708                     }
1709                 }
1710 
1711                 location = tracker.getLocation(key);
1712             }
1713 
1714             if (location == null) {
1715                 location = tracker.getLocation(EMPTY);
1716             }
1717         }
1718 
1719         return location;
1720     }
1721 
1722     private static boolean equals(String s1, String s2) {
1723         return StringUtils.clean(s1).equals(StringUtils.clean(s2));
1724     }
1725 
1726     private static Severity getSeverity(ModelBuildingRequest request, int errorThreshold) {
1727         return getSeverity(request.getValidationLevel(), errorThreshold);
1728     }
1729 
1730     private static Severity getSeverity(int validationLevel, int errorThreshold) {
1731         if (validationLevel < errorThreshold) {
1732             return Severity.WARNING;
1733         } else {
1734             return Severity.ERROR;
1735         }
1736     }
1737 }