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