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