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