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