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