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