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