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