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