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