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 + ( dependency.getClassifier() != null ? ":" + dependency.getClassifier() : "" );
558 String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
559 if ( key.equals( mKey ) )
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 else
602 {
603 validateEnum( prefix + "scope", problems, Severity.WARNING, Version.V20, d.getScope(),
604 d.getManagementKey(), d, "provided", "compile", "runtime", "test", "system",
605 "import" );
606 }
607 }
608 }
609 }
610
611 private void validateEffectiveModelAgainstDependency( String prefix, ModelProblemCollector problems, Model m,
612 Dependency d, ModelBuildingRequest request )
613 {
614 String key = d.getGroupId() + ":" + d.getArtifactId() + ":" + d.getVersion()
615 + ( d.getClassifier() != null ? ":" + d.getClassifier() : "" );
616 String mKey = m.getGroupId() + ":" + m.getArtifactId() + ":" + m.getVersion();
617 if ( key.equals( mKey ) )
618 {
619
620
621
622 addViolation( problems, Severity.FATAL, Version.V31, prefix + " " + key, key, "is referencing itself.", d );
623
624 }
625
626 }
627
628 private void validate20EffectivePluginDependencies( ModelProblemCollector problems, Plugin plugin,
629 ModelBuildingRequest request )
630 {
631 List<Dependency> dependencies = plugin.getDependencies();
632
633 if ( !dependencies.isEmpty() )
634 {
635 String prefix = "build.plugins.plugin[" + plugin.getKey() + "].dependencies.dependency.";
636
637 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 );
638
639 for ( Dependency d : dependencies )
640 {
641 validateEffectiveDependency( problems, d, false, prefix, request );
642
643 validateVersion( prefix + "version", problems, errOn30, Version.BASE, d.getVersion(),
644 d.getManagementKey(), d );
645
646 validateEnum( prefix + "scope", problems, errOn30, Version.BASE, d.getScope(), d.getManagementKey(), d,
647 "compile", "runtime", "system" );
648 }
649 }
650 }
651
652 private void validateEffectiveDependency( ModelProblemCollector problems, Dependency d, boolean management,
653 String prefix, ModelBuildingRequest request )
654 {
655 validateId( prefix + "artifactId", problems, Severity.ERROR, Version.BASE, d.getArtifactId(),
656 d.getManagementKey(), d );
657
658 validateId( prefix + "groupId", problems, Severity.ERROR, Version.BASE, d.getGroupId(), d.getManagementKey(),
659 d );
660
661 if ( !management )
662 {
663 validateStringNotEmpty( prefix + "type", problems, Severity.ERROR, Version.BASE, d.getType(),
664 d.getManagementKey(), d );
665
666 validateDependencyVersion( problems, d, prefix );
667 }
668
669 if ( "system".equals( d.getScope() ) )
670 {
671 String systemPath = d.getSystemPath();
672
673 if ( StringUtils.isEmpty( systemPath ) )
674 {
675 addViolation( problems, Severity.ERROR, Version.BASE, prefix + "systemPath", d.getManagementKey(),
676 "is missing.", d );
677 }
678 else
679 {
680 File sysFile = new File( systemPath );
681 if ( !sysFile.isAbsolute() )
682 {
683 addViolation( problems, Severity.ERROR, Version.BASE, prefix + "systemPath", d.getManagementKey(),
684 "must specify an absolute path but is " + systemPath, d );
685 }
686 else if ( !sysFile.isFile() )
687 {
688 String msg = "refers to a non-existing file " + sysFile.getAbsolutePath();
689 systemPath = systemPath.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
690 String jdkHome =
691 request.getSystemProperties().getProperty( "java.home", "" ) + File.separator + "..";
692 if ( systemPath.startsWith( jdkHome ) )
693 {
694 msg += ". Please verify that you run Maven using a JDK and not just a JRE.";
695 }
696 addViolation( problems, Severity.WARNING, Version.BASE, prefix + "systemPath", d.getManagementKey(),
697 msg, d );
698 }
699 }
700 }
701 else if ( StringUtils.isNotEmpty( d.getSystemPath() ) )
702 {
703 addViolation( problems, Severity.ERROR, Version.BASE, prefix + "systemPath", d.getManagementKey(),
704 "must be omitted." + " This field may only be specified for a dependency with system scope.",
705 d );
706 }
707
708 if ( request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 )
709 {
710 for ( Exclusion exclusion : d.getExclusions() )
711 {
712 if ( request.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 )
713 {
714 validateId( prefix + "exclusions.exclusion.groupId", problems, Severity.WARNING, Version.V20,
715 exclusion.getGroupId(), d.getManagementKey(), exclusion );
716
717 validateId( prefix + "exclusions.exclusion.artifactId", problems, Severity.WARNING, Version.V20,
718 exclusion.getArtifactId(), d.getManagementKey(), exclusion );
719 }
720 else
721 {
722 validateIdWithWildcards( prefix + "exclusions.exclusion.groupId", problems, Severity.WARNING,
723 Version.V30, exclusion.getGroupId(), d.getManagementKey(), exclusion );
724
725 validateIdWithWildcards( prefix + "exclusions.exclusion.artifactId", problems, Severity.WARNING,
726 Version.V30, exclusion.getArtifactId(), d.getManagementKey(), exclusion );
727 }
728 }
729 }
730 }
731
732
733
734
735 protected void validateDependencyVersion( ModelProblemCollector problems, Dependency d, String prefix )
736 {
737 validateStringNotEmpty( prefix + "version", problems, Severity.ERROR, Version.BASE, d.getVersion(),
738 d.getManagementKey(), d );
739 }
740
741 private void validateRawRepositories( ModelProblemCollector problems, List<Repository> repositories, String prefix,
742 ModelBuildingRequest request )
743 {
744 Map<String, Repository> index = new HashMap<>();
745
746 for ( Repository repository : repositories )
747 {
748 validateStringNotEmpty( prefix + ".id", problems, Severity.ERROR, Version.V20, repository.getId(),
749 repository );
750
751 validateStringNotEmpty( prefix + "[" + repository.getId() + "].url", problems, Severity.ERROR, Version.V20,
752 repository.getUrl(), repository );
753
754 String key = repository.getId();
755
756 Repository existing = index.get( key );
757
758 if ( existing != null )
759 {
760 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 );
761
762 addViolation( problems, errOn30, Version.V20, prefix + ".id", null, "must be unique: "
763 + repository.getId() + " -> " + existing.getUrl() + " vs " + repository.getUrl(), repository );
764 }
765 else
766 {
767 index.put( key, repository );
768 }
769 }
770 }
771
772 private void validate20EffectiveRepository( ModelProblemCollector problems, Repository repository, String prefix,
773 ModelBuildingRequest request )
774 {
775 if ( repository != null )
776 {
777 Severity errOn31 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1 );
778
779 validateBannedCharacters( prefix + ".id", problems, errOn31, Version.V20, repository.getId(), null,
780 repository, ILLEGAL_REPO_ID_CHARS );
781
782 if ( "local".equals( repository.getId() ) )
783 {
784 addViolation( problems, errOn31, Version.V20, prefix + ".id", null,
785 "must not be 'local'" + ", this identifier is reserved for the local repository"
786 + ", using it for other repositories will corrupt your repository metadata.",
787 repository );
788 }
789
790 if ( "legacy".equals( repository.getLayout() ) )
791 {
792 addViolation( problems, Severity.WARNING, Version.V20, prefix + ".layout", repository.getId(),
793 "uses the unsupported value 'legacy', artifact resolution might fail.", repository );
794 }
795 }
796 }
797
798 private void validate20RawResources( ModelProblemCollector problems, List<Resource> resources, String prefix,
799 ModelBuildingRequest request )
800 {
801 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 );
802
803 for ( Resource resource : resources )
804 {
805 validateStringNotEmpty( prefix + ".directory", problems, Severity.ERROR, Version.V20,
806 resource.getDirectory(), resource );
807
808 validateBoolean( prefix + ".filtering", problems, errOn30, Version.V20, resource.getFiltering(),
809 resource.getDirectory(), resource );
810 }
811 }
812
813
814
815
816
817 private boolean validateId( String fieldName, ModelProblemCollector problems, String id,
818 InputLocationTracker tracker )
819 {
820 return validateId( fieldName, problems, Severity.ERROR, Version.BASE, id, null, tracker );
821 }
822
823 private boolean validateId( String fieldName, ModelProblemCollector problems, Severity severity, Version version,
824 String id, String sourceHint, InputLocationTracker tracker )
825 {
826 if ( !validateStringNotEmpty( fieldName, problems, severity, version, id, sourceHint, tracker ) )
827 {
828 return false;
829 }
830 else
831 {
832 boolean match = ID_REGEX.matcher( id ).matches();
833 if ( !match )
834 {
835 addViolation( problems, severity, version, fieldName, sourceHint,
836 "with value '" + id + "' does not match a valid id pattern.", tracker );
837 }
838 return match;
839 }
840 }
841
842 private boolean validateIdWithWildcards( String fieldName, ModelProblemCollector problems, Severity severity,
843 Version version, String id, String sourceHint,
844 InputLocationTracker tracker )
845 {
846 if ( !validateStringNotEmpty( fieldName, problems, severity, version, id, sourceHint, tracker ) )
847 {
848 return false;
849 }
850 else
851 {
852 boolean match = ID_WITH_WILDCARDS_REGEX.matcher( id ).matches();
853 if ( !match )
854 {
855 addViolation( problems, severity, version, fieldName, sourceHint,
856 "with value '" + id + "' does not match a valid id pattern.", tracker );
857 }
858 return match;
859 }
860 }
861
862 private boolean validateStringNoExpression( String fieldName, ModelProblemCollector problems, Severity severity,
863 Version version, String string, InputLocationTracker tracker )
864 {
865 if ( !hasExpression( string ) )
866 {
867 return true;
868 }
869
870 addViolation( problems, severity, version, fieldName, null, "contains an expression but should be a constant.",
871 tracker );
872
873 return false;
874 }
875
876 private boolean validateVersionNoExpression( String fieldName, ModelProblemCollector problems, Severity severity,
877 Version version, String string, InputLocationTracker tracker )
878 {
879 if ( !hasExpression( string ) )
880 {
881 return true;
882 }
883
884
885
886
887
888
889
890
891 Matcher m = CI_FRIENDLY_EXPRESSION.matcher( string.trim() );
892 while ( m.find() )
893 {
894 if ( !CI_FRIENDLY_POSSIBLE_PROPERTY_NAMES.contains( m.group( 1 ) ) )
895 {
896 addViolation( problems, severity, version, fieldName, null,
897 "contains an expression but should be a constant.", tracker );
898
899 return false;
900 }
901 }
902
903 return true;
904 }
905
906 private boolean hasExpression( String value )
907 {
908 return value != null && value.contains( "${" );
909 }
910
911 private boolean hasProjectExpression( String value )
912 {
913 return value != null && value.contains( "${project." );
914 }
915
916 private boolean validateStringNotEmpty( String fieldName, ModelProblemCollector problems, Severity severity,
917 Version version, String string, InputLocationTracker tracker )
918 {
919 return validateStringNotEmpty( fieldName, problems, severity, version, string, null, tracker );
920 }
921
922
923
924
925
926
927
928
929
930 private boolean validateStringNotEmpty( String fieldName, ModelProblemCollector problems, Severity severity,
931 Version version, String string, String sourceHint,
932 InputLocationTracker tracker )
933 {
934 if ( !validateNotNull( fieldName, problems, severity, version, string, sourceHint, tracker ) )
935 {
936 return false;
937 }
938
939 if ( string.length() > 0 )
940 {
941 return true;
942 }
943
944 addViolation( problems, severity, version, fieldName, sourceHint, "is missing.", tracker );
945
946 return false;
947 }
948
949
950
951
952
953
954
955
956 private boolean validateNotNull( String fieldName, ModelProblemCollector problems, Severity severity,
957 Version version, Object object, String sourceHint, InputLocationTracker tracker )
958 {
959 if ( object != null )
960 {
961 return true;
962 }
963
964 addViolation( problems, severity, version, fieldName, sourceHint, "is missing.", tracker );
965
966 return false;
967 }
968
969 private boolean validateBoolean( String fieldName, ModelProblemCollector problems, Severity severity,
970 Version version, String string, String sourceHint, InputLocationTracker tracker )
971 {
972 if ( string == null || string.length() <= 0 )
973 {
974 return true;
975 }
976
977 if ( "true".equalsIgnoreCase( string ) || "false".equalsIgnoreCase( string ) )
978 {
979 return true;
980 }
981
982 addViolation( problems, severity, version, fieldName, sourceHint,
983 "must be 'true' or 'false' but is '" + string + "'.", tracker );
984
985 return false;
986 }
987
988 @SuppressWarnings( "checkstyle:parameternumber" )
989 private boolean validateEnum( String fieldName, ModelProblemCollector problems, Severity severity, Version version,
990 String string, String sourceHint, InputLocationTracker tracker,
991 String... validValues )
992 {
993 if ( string == null || string.length() <= 0 )
994 {
995 return true;
996 }
997
998 List<String> values = Arrays.asList( validValues );
999
1000 if ( values.contains( string ) )
1001 {
1002 return true;
1003 }
1004
1005 addViolation( problems, severity, version, fieldName, sourceHint,
1006 "must be one of " + values + " but is '" + string + "'.", tracker );
1007
1008 return false;
1009 }
1010
1011 @SuppressWarnings( "checkstyle:parameternumber" )
1012 private boolean validateBannedCharacters( String fieldName, ModelProblemCollector problems, Severity severity,
1013 Version version, String string, String sourceHint,
1014 InputLocationTracker tracker, String banned )
1015 {
1016 if ( string != null )
1017 {
1018 for ( int i = string.length() - 1; i >= 0; i-- )
1019 {
1020 if ( banned.indexOf( string.charAt( i ) ) >= 0 )
1021 {
1022 addViolation( problems, severity, version, fieldName, sourceHint,
1023 "must not contain any of these characters " + banned + " but found "
1024 + string.charAt( i ),
1025 tracker );
1026 return false;
1027 }
1028 }
1029 }
1030
1031 return true;
1032 }
1033
1034 private boolean validateVersion( String fieldName, ModelProblemCollector problems, Severity severity,
1035 Version version, String string, String sourceHint, InputLocationTracker tracker )
1036 {
1037 if ( string == null || string.length() <= 0 )
1038 {
1039 return true;
1040 }
1041
1042 if ( hasExpression( string ) )
1043 {
1044 addViolation( problems, severity, version, fieldName, sourceHint,
1045 "must be a valid version but is '" + string + "'.", tracker );
1046 return false;
1047 }
1048
1049 return validateBannedCharacters( fieldName, problems, severity, version, string, sourceHint, tracker,
1050 ILLEGAL_VERSION_CHARS );
1051
1052 }
1053
1054 private boolean validate20ProperSnapshotVersion( String fieldName, ModelProblemCollector problems,
1055 Severity severity, Version version, String string,
1056 String sourceHint, InputLocationTracker tracker )
1057 {
1058 if ( string == null || string.length() <= 0 )
1059 {
1060 return true;
1061 }
1062
1063 if ( string.endsWith( "SNAPSHOT" ) && !string.endsWith( "-SNAPSHOT" ) )
1064 {
1065 addViolation( problems, severity, version, fieldName, sourceHint,
1066 "uses an unsupported snapshot version format, should be '*-SNAPSHOT' instead.", tracker );
1067 return false;
1068 }
1069
1070 return true;
1071 }
1072
1073 private boolean validate20PluginVersion( String fieldName, ModelProblemCollector problems, String string,
1074 String sourceHint, InputLocationTracker tracker,
1075 ModelBuildingRequest request )
1076 {
1077 if ( string == null )
1078 {
1079
1080 return true;
1081 }
1082
1083 Severity errOn30 = getSeverity( request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0 );
1084
1085 if ( !validateVersion( fieldName, problems, errOn30, Version.V20, string, sourceHint, tracker ) )
1086 {
1087 return false;
1088 }
1089
1090 if ( string.length() <= 0 || "RELEASE".equals( string ) || "LATEST".equals( string ) )
1091 {
1092 addViolation( problems, errOn30, Version.V20, fieldName, sourceHint,
1093 "must be a valid version but is '" + string + "'.", tracker );
1094 return false;
1095 }
1096
1097 return true;
1098 }
1099
1100 private static void addViolation( ModelProblemCollector problems, Severity severity, Version version,
1101 String fieldName, String sourceHint, String message,
1102 InputLocationTracker tracker )
1103 {
1104 StringBuilder buffer = new StringBuilder( 256 );
1105 buffer.append( '\'' ).append( fieldName ).append( '\'' );
1106
1107 if ( sourceHint != null )
1108 {
1109 buffer.append( " for " ).append( sourceHint );
1110 }
1111
1112 buffer.append( ' ' ).append( message );
1113
1114
1115 problems.add( new ModelProblemCollectorRequest( severity, version ).setMessage(
1116 buffer.toString() ).setLocation( getLocation( fieldName, tracker ) ) );
1117
1118 }
1119
1120 private static InputLocation getLocation( String fieldName, InputLocationTracker tracker )
1121 {
1122 InputLocation location = null;
1123
1124 if ( tracker != null )
1125 {
1126 if ( fieldName != null )
1127 {
1128 Object key = fieldName;
1129
1130 int idx = fieldName.lastIndexOf( '.' );
1131 if ( idx >= 0 )
1132 {
1133 fieldName = fieldName.substring( idx + 1 );
1134 key = fieldName;
1135 }
1136
1137 if ( fieldName.endsWith( "]" ) )
1138 {
1139 key = fieldName.substring( fieldName.lastIndexOf( '[' ) + 1, fieldName.length() - 1 );
1140 try
1141 {
1142 key = Integer.valueOf( key.toString() );
1143 }
1144 catch ( NumberFormatException e )
1145 {
1146
1147 }
1148 }
1149
1150 location = tracker.getLocation( key );
1151 }
1152
1153 if ( location == null )
1154 {
1155 location = tracker.getLocation( "" );
1156 }
1157 }
1158
1159 return location;
1160 }
1161
1162 private static boolean equals( String s1, String s2 )
1163 {
1164 return StringUtils.clean( s1 ).equals( StringUtils.clean( s2 ) );
1165 }
1166
1167 private static Severity getSeverity( ModelBuildingRequest request, int errorThreshold )
1168 {
1169 return getSeverity( request.getValidationLevel(), errorThreshold );
1170 }
1171
1172 private static Severity getSeverity( int validationLevel, int errorThreshold )
1173 {
1174 if ( validationLevel < errorThreshold )
1175 {
1176 return Severity.WARNING;
1177 }
1178 else
1179 {
1180 return Severity.ERROR;
1181 }
1182 }
1183
1184 }