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