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