View Javadoc

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