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