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