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