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