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