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