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