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