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