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