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