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