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