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