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