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