View Javadoc
1   package org.apache.maven.plugin.doap;
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 org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.factory.ArtifactFactory;
24  import org.apache.maven.artifact.repository.ArtifactRepository;
25  import org.apache.maven.artifact.repository.metadata.ArtifactRepositoryMetadata;
26  import org.apache.maven.artifact.repository.metadata.RepositoryMetadata;
27  import org.apache.maven.artifact.repository.metadata.RepositoryMetadataManager;
28  import org.apache.maven.artifact.repository.metadata.RepositoryMetadataResolutionException;
29  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
30  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
31  import org.apache.maven.artifact.resolver.ArtifactResolver;
32  import org.apache.maven.model.Contributor;
33  import org.apache.maven.model.Developer;
34  import org.apache.maven.model.License;
35  import org.apache.maven.plugin.AbstractMojo;
36  import org.apache.maven.plugin.MojoExecutionException;
37  import org.apache.maven.plugin.doap.options.ASFExtOptions;
38  import org.apache.maven.plugin.doap.options.ASFExtOptionsUtil;
39  import org.apache.maven.plugin.doap.options.DoapArtifact;
40  import org.apache.maven.plugin.doap.options.DoapOptions;
41  import org.apache.maven.plugin.doap.options.ExtOptions;
42  import org.apache.maven.plugin.doap.options.Standard;
43  import org.apache.maven.plugins.annotations.Component;
44  import org.apache.maven.plugins.annotations.Mojo;
45  import org.apache.maven.plugins.annotations.Parameter;
46  import org.apache.maven.project.MavenProject;
47  import org.apache.maven.project.MavenProjectBuilder;
48  import org.apache.maven.project.ProjectBuildingException;
49  import org.apache.maven.scm.manager.NoSuchScmProviderException;
50  import org.apache.maven.scm.manager.ScmManager;
51  import org.apache.maven.scm.provider.cvslib.repository.CvsScmProviderRepository;
52  import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
53  import org.apache.maven.scm.repository.ScmRepository;
54  import org.apache.maven.scm.repository.ScmRepositoryException;
55  import org.apache.maven.settings.Settings;
56  import org.codehaus.plexus.i18n.I18N;
57  import org.codehaus.plexus.util.FileUtils;
58  import org.codehaus.plexus.util.StringUtils;
59  import org.codehaus.plexus.util.WriterFactory;
60  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
61  import org.codehaus.plexus.util.xml.XMLWriter;
62  
63  import java.io.File;
64  import java.io.IOException;
65  import java.io.Writer;
66  import java.net.MalformedURLException;
67  import java.net.URL;
68  import java.text.DateFormat;
69  import java.text.ParseException;
70  import java.text.SimpleDateFormat;
71  import java.util.ArrayList;
72  import java.util.Arrays;
73  import java.util.Collections;
74  import java.util.Comparator;
75  import java.util.Date;
76  import java.util.List;
77  import java.util.Locale;
78  import java.util.Map;
79  import java.util.Map.Entry;
80  import java.util.Set;
81  import java.util.TimeZone;
82  
83  /**
84   * Generate a <a href="http://usefulinc.com/ns/doap">Description of a Project (DOAP)</a> file from the main information
85   * found in a POM. <br/>
86   * <b>Note</b>: The generated file is tailored for use by projects at <a
87   * href="http://projects.apache.org/doap.html">Apache</a>.
88   *
89   * @author Jason van Zyl
90   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
91   * @version $Id: DoapMojo.java 1642247 2014-11-27 23:17:01Z hboutemy $
92   * @since 1.0-beta-1
93   */
94  @Mojo( name = "generate" )
95  public class DoapMojo
96      extends AbstractMojo
97  {
98      /**
99       * UTC Time Zone
100      */
101     private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone( "UTC" );
102 
103     /**
104      * Date format for <lastUpdated/> tag in the repository metadata, i.e.: yyyyMMddHHmmss
105      */
106     private static final DateFormat REPOSITORY_DATE_FORMAT;
107 
108     /**
109      * Date format for DOAP file, i.e. ISO-8601 YYYY-MM-DD
110      */
111     private static final DateFormat DOAP_DATE_FORMAT;
112 
113     static
114     {
115         REPOSITORY_DATE_FORMAT = new SimpleDateFormat( "yyyyMMddHHmmss", Locale.ENGLISH );
116         REPOSITORY_DATE_FORMAT.setTimeZone( UTC_TIME_ZONE );
117 
118         DOAP_DATE_FORMAT = new SimpleDateFormat( "yyyy-MM-dd", Locale.ENGLISH );
119         DOAP_DATE_FORMAT.setTimeZone( UTC_TIME_ZONE );
120     }
121 
122     // ----------------------------------------------------------------------
123     // Mojo components
124     // ----------------------------------------------------------------------
125 
126     /**
127      * Maven SCM Manager.
128      *
129      * @since 1.0
130      */
131     @Component
132     private ScmManager scmManager;
133 
134     /**
135      * Artifact factory.
136      *
137      * @since 1.0
138      */
139     @Component
140     private ArtifactFactory artifactFactory;
141 
142     /**
143      * Used to resolve artifacts.
144      *
145      * @since 1.0
146      */
147     @Component
148     private RepositoryMetadataManager repositoryMetadataManager;
149 
150     /**
151      * Internationalization component.
152      *
153      * @since 1.0
154      */
155     @Component
156     private I18N i18n;
157 
158     // ----------------------------------------------------------------------
159     // Mojo parameters
160     // ----------------------------------------------------------------------
161 
162     /**
163      * The POM from which information will be extracted to create a DOAP file.
164      */
165     @Parameter( defaultValue = "${project}", readonly = true, required = true )
166     private MavenProject project;
167 
168     /**
169      * The name of the DOAP file that will be generated.
170      */
171     @Parameter( property = "doapFile", defaultValue = "doap_${project.artifactId}.rdf", required = true )
172     private String doapFile;
173 
174     /**
175      * The output directory of the DOAP file that will be generated.
176      *
177      * @since 1.1
178      */
179     @Parameter( defaultValue = "${project.reporting.outputDirectory}", required = true )
180     private String outputDirectory;
181 
182     /**
183      * The local repository where the artifacts are located.
184      *
185      * @since 1.0
186      */
187     @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
188     private ArtifactRepository localRepository;
189 
190     /**
191      * The remote repositories where the artifacts are located.
192      *
193      * @since 1.0
194      */
195     @Parameter( defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true )
196     private List<ArtifactRepository> remoteRepositories;
197 
198     /**
199      * Factory for creating artifact objects
200      *
201      * @since 1.1
202      */
203     @Component
204     private ArtifactFactory factory;
205 
206     /**
207      * Project builder
208      *
209      * @since 1.1
210      */
211     @Component
212     private MavenProjectBuilder mavenProjectBuilder;
213 
214     /**
215      * Used for resolving artifacts
216      *
217      * @since 1.1
218      */
219     @Component
220     private ArtifactResolver resolver;
221 
222     /**
223      * The current user system settings for use in Maven.
224      *
225      * @since 1.1
226      */
227     @Parameter( defaultValue = "${settings}", readonly = true, required = true )
228     protected Settings settings;
229 
230     // ----------------------------------------------------------------------
231     // Doap options
232     // ----------------------------------------------------------------------
233 
234     /**
235      * The category which should be displayed in the DOAP file.
236      *
237      * @deprecated Since 1.0. Instead of, configure
238      *             <code>&lt;doapOptions&gt;&lt;category/&gt;&lt;/doapOptions&gt;</code> parameter.
239      */
240     @Parameter( property = "category" )
241     private String category;
242 
243     /**
244      * The programming language which should be displayed in the DOAP file.
245      *
246      * @deprecated Since 1.0. Instead of, configure
247      *             <code>&lt;doapOptions&gt;&lt;programmingLanguage/&gt;&lt;/doapOptions&gt;</code> parameter.
248      */
249     @Parameter( property = "language" )
250     private String language;
251 
252     /**
253      * Specific DOAP parameters, i.e. options that POM doesn't have any notions. <br/>
254      * Example:
255      * <p/>
256      * <pre>
257      * &lt;doapOptions&gt;
258      * &nbsp;&nbsp;&lt;programmingLanguage&gt;java&lt;/programmingLanguage&gt;
259      * &lt;/doapOptions&gt;
260      * </pre>
261      * <p/>
262      * <br/>
263      * See <a href="./apidocs/org/apache/maven/plugin/doap/options/DoapOptions.html">Javadoc</a> <br/>
264      *
265      * @see <a href="http://usefulinc.com/ns/doap#">http://usefulinc.com/ns/doap#</a>
266      * @since 1.0
267      */
268     @Parameter( property = "doapOptions" )
269     private DoapOptions doapOptions;
270 
271     /**
272      * Specific ASF extensions parameters, i.e. options that POM doesn't have any notions but required by ASF DOAP
273      * requirements. <br/>
274      * Example:
275      * <p/>
276      * <pre>
277      * &lt;asfExtOptions&gt;
278      * &nbsp;&nbsp;&lt;included&gt;true&lt;/included&gt;
279      * &nbsp;&nbsp;&lt;charter&gt;The mission of the Apache XXX project is to create and maintain software
280      * &nbsp;&nbsp;libraries that provide ...&lt;/charter&gt;
281      * &nbsp;&nbsp;...
282      * &lt;/asfExtOptions&gt;
283      * </pre>
284      * <p/>
285      * <b>Note</b>: By default, <code>&lt;asfExtOptions&gt;&lt;included/&gt;&lt;/asfExtOptions&gt;</code> will be
286      * automatically set to <code>true</code> if the project is hosted at ASF. <br/>
287      * See <a href="./apidocs/org/apache/maven/plugin/doap/options/ASFExtOptions.html">Javadoc</a> <br/>
288      *
289      * @see <a href="http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext">
290      *      http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext</a>;
291      * @see <a href="http://projects.apache.org/docs/pmc.html">http://projects.apache.org/docs/pmc.html</a>
292      * @see <a href="http://projects.apache.org/docs/standards.html">http://projects.apache.org/docs/standards.html</a>
293      * @see ASFExtOptionsUtil#isASFProject(MavenProject)
294      * @since 1.0
295      */
296     @Parameter( property = "asfExtOptions" )
297     private ASFExtOptions asfExtOptions;
298 
299     /**
300      * The value for the <code>xml:lang</code> attribute used by the <code>&lt;rdf:RDF/&gt;<code>,
301      * <code>&lt;description/&gt;</code> and <code>&lt;shortdesc/&gt;</code> elements. <br/>
302      * POM doesn't have any notions about language. <br/>
303      * See <a href="http://www.w3.org/TR/REC-xml/#sec-lang-tag">http://www.w3.org/TR/REC-xml/#sec-lang-tag</a> <br/>
304      *
305      * @since 1.0
306      */
307     @Parameter( property = "lang", defaultValue = "en", required = true )
308     private String lang;
309 
310     /**
311      * The <code>about</code> URI-reference which should be displayed in the DOAP file. Example:
312      * <p/>
313      * <pre>
314      * &lt;rdf:RDF&gt;
315      * &nbsp;&nbsp;&lt;Project rdf:about="http://maven.apache.org/"&gt;
316      * &nbsp;&nbsp;...
317      * &nbsp;&nbsp;&lt;/Project&gt;
318      * &lt;/rdf:RDF&gt;
319      * </pre>
320      * <p/>
321      * See <a href="http://www.w3.org/TR/1999/REC-rdf-syntax-19990222/#aboutAttr">
322      * http://www.w3.org/TR/1999/REC-rdf-syntax-19990222/#aboutAttr</a> <br/>
323      *
324      * @since 1.0
325      */
326     @Parameter( property = "about", defaultValue = "${project.url}" )
327     private String about;
328 
329     /**
330      * Flag to validate the generated DOAP.
331      *
332      * @since 1.1
333      */
334     @Parameter( defaultValue = "true" )
335     private boolean validate;
336 
337     /**
338      * An artifact to generate the DOAP file against. <br/>
339      * Example:
340      * <p/>
341      * <pre>
342      * &lt;artifact&gt;
343      * &nbsp;&nbsp;&lt;groupId&gt;given-artifact-groupId&lt;/groupId&gt;
344      * &nbsp;&nbsp;&lt;artifactId&gt;given-artifact-artifactId&lt;/artifactId&gt;
345      * &nbsp;&nbsp;&lt;version&gt;given-artifact-version&lt;/version&gt;
346      * &lt;/artifact&gt;
347      * </pre>
348      * <p/>
349      * <br/>
350      * See <a href="./apidocs/org/apache/maven/plugin/doap/options/DaopArtifact.html">Javadoc</a> <br/>
351      *
352      * @since 1.1
353      */
354     @Parameter
355     private DoapArtifact artifact;
356 
357     /**
358      * Specifies whether the DOAP generation should be skipped.
359      *
360      * @since 1.1
361      */
362     @Parameter( property = "maven.doap.skip", defaultValue = "false" )
363     private boolean skip;
364 
365     /**
366      * Extensions parameters. <br/>
367      * Example:
368      * <p/>
369      * <pre>
370      * &lt;extOptions&gt;
371      * &nbsp;&lt;extOption&gt;
372      * &nbsp;&nbsp;&nbsp;&lt;xmlnsPrefix&gt;labs&lt;/xmlnsPrefix&gt;
373      * &nbsp;&nbsp;&nbsp;&lt;xmlnsNamespaceURI&gt;http://labs.apache.org/doap-ext/1.0#&lt;/xmlnsNamespaceURI&gt;
374      * &nbsp;&nbsp;&nbsp;&lt;extensions&gt;
375      * &nbsp;&nbsp;&nbsp;&nbsp;&lt;status&gt;active&lt;/status&gt;
376      * &nbsp;&nbsp;&nbsp;&lt;/extensions&gt;
377      * &nbsp;&lt;/extOption&gt;
378      * &lt;/extOptions&gt;
379      * </pre>
380      * <p/>
381      * See <a href="./apidocs/org/apache/maven/plugin/doap/options/ExtOptions.html">Javadoc</a> <br/>
382      *
383      * @since 1.1
384      */
385     @Parameter( property = "extOptions" )
386     private ExtOptions[] extOptions;
387 
388     /**
389      * All warn/error messages for the user.
390      *
391      * @since 1.1
392      */
393     private UserMessages messages = new UserMessages();
394 
395     // ----------------------------------------------------------------------
396     // Public methods
397     // ----------------------------------------------------------------------
398 
399     /**
400      * {@inheritDoc}
401      */
402     public void execute()
403         throws MojoExecutionException
404     {
405         if ( skip )
406         {
407             getLog().info( "Skipping DOAP generation" );
408             return;
409         }
410 
411         // single artifact
412         if ( artifact != null )
413         {
414             MavenProject givenProject = getMavenProject( artifact );
415             if ( givenProject != null )
416             {
417                 File outDir = new File( outputDirectory );
418                 if ( !outDir.isAbsolute() )
419                 {
420                     outDir = new File( project.getBasedir(), outputDirectory );
421                 }
422                 File outFile = new File( outDir, artifact.getDoapFileName() );
423                 writeDoapFile( givenProject, outFile );
424                 return;
425             }
426         }
427 
428         // current project
429         File outFile = new File( doapFile );
430         if ( !outFile.isAbsolute() )
431         {
432             outFile = new File( project.getBasedir(), doapFile );
433         }
434         if ( !doapFile.replaceAll( "\\\\", "/" ).contains( "/" ) )
435         {
436             File outDir = new File( outputDirectory );
437             if ( !outDir.isAbsolute() )
438             {
439                 outDir = new File( project.getBasedir(), outputDirectory );
440             }
441             outFile = new File( outDir, doapFile );
442         }
443         writeDoapFile( project, outFile );
444     }
445 
446     // ----------------------------------------------------------------------
447     // Private methods
448     // ----------------------------------------------------------------------
449 
450     /**
451      * @param artifact not null
452      * @return the maven project for the given doap artifact
453      * @since 1.1
454      */
455     private MavenProject getMavenProject( DoapArtifact artifact )
456     {
457         if ( artifact == null )
458         {
459             return null;
460         }
461 
462         if ( StringUtils.isEmpty( artifact.getGroupId() ) || StringUtils.isEmpty( artifact.getArtifactId() )
463             || StringUtils.isEmpty( artifact.getVersion() ) )
464         {
465             getLog().warn( "Missing groupId or artifactId or version in <artifact/> parameter, ignored it." );
466             return null;
467         }
468 
469         getLog().info(
470             "Using artifact " + artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion() );
471 
472         try
473         {
474             Artifact art =
475                 factory.createProjectArtifact( artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(),
476                                                Artifact.SCOPE_COMPILE );
477 
478             if ( art.getFile() == null )
479             {
480                 MavenProject proj = mavenProjectBuilder.buildFromRepository( art, remoteRepositories, localRepository );
481                 art = proj.getArtifact();
482 
483                 resolver.resolve( art, remoteRepositories, localRepository );
484 
485                 return proj;
486             }
487         }
488         catch ( ArtifactResolutionException e )
489         {
490             getLog().error( "ArtifactResolutionException: " + e.getMessage() + "\nIgnored <artifact/> parameter." );
491         }
492         catch ( ArtifactNotFoundException e )
493         {
494             getLog().error( "ArtifactNotFoundException: " + e.getMessage() + "\nIgnored <artifact/> parameter." );
495         }
496         catch ( ProjectBuildingException e )
497         {
498             getLog().error( "ProjectBuildingException: " + e.getMessage() + "\nIgnored <artifact/> parameter." );
499         }
500 
501         return null;
502     }
503 
504     /**
505      * Write a doap file for the given project.
506      *
507      * @param project    not null
508      * @param outputFile not null
509      * @since 1.1
510      */
511     private void writeDoapFile( MavenProject project, File outputFile )
512         throws MojoExecutionException
513     {
514         // ----------------------------------------------------------------------------
515         // Includes ASF extensions
516         // ----------------------------------------------------------------------------
517 
518         if ( !asfExtOptions.isIncluded() && ASFExtOptionsUtil.isASFProject( project ) )
519         {
520             getLog().info( "This project is an ASF project, ASF Extensions to DOAP will be added." );
521             asfExtOptions.setIncluded( true );
522         }
523 
524         // ----------------------------------------------------------------------------
525         // setup pretty print xml writer
526         // ----------------------------------------------------------------------------
527 
528         Writer w;
529         try
530         {
531             if ( !outputFile.getParentFile().exists() )
532             {
533                 FileUtils.mkdir( outputFile.getParentFile().getAbsolutePath() );
534             }
535 
536             w = WriterFactory.newXmlWriter( outputFile );
537         }
538         catch ( IOException e )
539         {
540             throw new MojoExecutionException( "Error creating DOAP file " + outputFile.getAbsolutePath(), e );
541         }
542 
543         if ( asfExtOptions.isIncluded() )
544         {
545             getLog().info( "Generating an ASF DOAP file " + outputFile.getAbsolutePath() );
546         }
547         else
548         {
549             getLog().info( "Generating a pure DOAP file " + outputFile.getAbsolutePath() );
550         }
551 
552         XMLWriter writer = new PrettyPrintXMLWriter( w, project.getModel().getModelEncoding(), null );
553 
554         // ----------------------------------------------------------------------------
555         // Convert POM to DOAP
556         // ----------------------------------------------------------------------------
557 
558         DoapUtil.writeHeader( writer );
559 
560         // Heading
561         DoapUtil.writeStartElement( writer, "rdf", "RDF" );
562         if ( Arrays.binarySearch( Locale.getISOLanguages(), lang ) < 0 )
563         {
564             messages.addMessage( new String[]{ "doapOptions", "lang" }, lang, UserMessages.INVALID_ISO_DATE );
565             throw new MojoExecutionException( messages.getErrorMessages().get( 0 ) );
566         }
567         writer.addAttribute( "xml:lang", lang );
568         if ( StringUtils.isEmpty( doapOptions.getXmlnsNamespaceURI() ) )
569         {
570             messages.addMessage( new String[]{ "doapOptions", "xmlnsNamespaceURI" }, null, UserMessages.REQUIRED );
571             throw new MojoExecutionException( messages.getErrorMessages().get( 0 ) );
572         }
573         writer.addAttribute(
574             "xmlns" + ( StringUtils.isEmpty( doapOptions.getXmlnsPrefix() ) ? "" : ":" + doapOptions.getXmlnsPrefix() ),
575             doapOptions.getXmlnsNamespaceURI() );
576         writer.addAttribute( "xmlns:rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#" );
577         writer.addAttribute( "xmlns:foaf", "http://xmlns.com/foaf/0.1/" );
578         if ( asfExtOptions.isIncluded() )
579         {
580             if ( StringUtils.isEmpty( asfExtOptions.getXmlnsPrefix() ) )
581             {
582                 messages.addMessage( new String[]{ "doapOptions", "xmlnsPrefix" }, null, UserMessages.REQUIRED );
583                 throw new MojoExecutionException( messages.getErrorMessages().get( 0 ) );
584             }
585             if ( StringUtils.isEmpty( asfExtOptions.getXmlnsNamespaceURI() ) )
586             {
587                 messages.addMessage( new String[]{ "doapOptions", "xmlnsNamespaceURI" }, null, UserMessages.REQUIRED );
588             }
589             writer.addAttribute( "xmlns" + ( StringUtils.isEmpty( asfExtOptions.getXmlnsPrefix() )
590                 ? ""
591                 : ":" + asfExtOptions.getXmlnsPrefix() ), asfExtOptions.getXmlnsNamespaceURI() );
592         }
593         if ( extOptions != null && extOptions.length > 0 && !extOptions[0].getExtensions().isEmpty() )
594         {
595             for ( ExtOptions extOption : extOptions )
596             {
597                 if ( StringUtils.isEmpty( extOption.getXmlnsPrefix() ) )
598                 {
599                     messages.addMessage( new String[]{ "extOptions", "extOption", "xmlnsPrefix" }, null,
600                                          UserMessages.REQUIRED );
601                     throw new MojoExecutionException( messages.getErrorMessages().get( 0 ) );
602                 }
603                 if ( StringUtils.isEmpty( extOption.getXmlnsNamespaceURI() ) )
604                 {
605                     messages.addMessage( new String[]{ "extOptions", "extOption", "xmlnsNamespaceURI" }, null,
606                                          UserMessages.REQUIRED );
607                     throw new MojoExecutionException( messages.getErrorMessages().get( 0 ) );
608                 }
609                 writer.addAttribute( "xmlns" + ( StringUtils.isEmpty( extOption.getXmlnsPrefix() )
610                     ? ""
611                     : ":" + extOption.getXmlnsPrefix() ), extOption.getXmlnsNamespaceURI() );
612             }
613         }
614 
615         // Project
616         DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "Project" );
617         boolean added = false;
618         if ( artifact != null )
619         {
620             String about = project.getUrl();
621 
622             if ( StringUtils.isNotEmpty( about ) )
623             {
624                 try
625                 {
626                     new URL( about );
627 
628                     writer.addAttribute( "rdf:about", about );
629                     added = true;
630                 }
631                 catch ( MalformedURLException e )
632                 {
633                     // ignore
634                 }
635             }
636 
637             if ( !added )
638             {
639                 messages.getWarnMessages().add( "The project's url defined from " + artifact.toConfiguration()
640                                                     + " is empty or not a valid URL, using <about/> parameter." );
641             }
642         }
643 
644         if ( !added )
645         {
646             if ( StringUtils.isNotEmpty( about ) )
647             {
648                 try
649                 {
650                     new URL( about );
651 
652                     writer.addAttribute( "rdf:about", about );
653                 }
654                 catch ( MalformedURLException e )
655                 {
656                     messages.addMessage( new String[]{ "about" }, about, UserMessages.INVALID_URL );
657                 }
658                 added = true;
659             }
660         }
661 
662         if ( !added )
663         {
664             messages.addMessage( new String[]{ "about" }, null, UserMessages.RECOMMENDED );
665         }
666 
667         // name
668         writeName( writer, project );
669 
670         // description
671         writeDescription( writer, project );
672 
673         // implements
674         writeImplements( writer );
675 
676         // Audience
677         writeAudience( writer );
678 
679         // Vendor
680         writeVendor( writer, project );
681 
682         // created
683         writeCreated( writer, project );
684 
685         // homepage and old-homepage
686         writeHomepage( writer, project );
687 
688         // Blog
689         writeBlog( writer );
690 
691         // licenses
692         writeLicenses( writer, project );
693 
694         // programming-language
695         writeProgrammingLanguage( writer, project );
696 
697         // category
698         writeCategory( writer, project );
699 
700         // os
701         writeOS( writer, project );
702 
703         // Plateform
704         writePlateform( writer );
705 
706         // Language
707         writeLanguage( writer );
708 
709         // SCM
710         writeSourceRepositories( writer, project );
711 
712         // bug-database
713         writeBugDatabase( writer, project );
714 
715         // mailing list
716         writeMailingList( writer, project );
717 
718         // download-page and download-mirror
719         writeDownloadPage( writer, project );
720 
721         // screenshots
722         writeScreenshots( writer, project );
723 
724         // service-endpoint
725         writeServiceEndpoint( writer );
726 
727         // wiki
728         writeWiki( writer, project );
729 
730         // Releases
731         writeReleases( writer, project );
732 
733         // Developers
734         List<Contributor> developers = project.getDevelopers();
735         writeContributors( writer, developers );
736 
737         // Contributors
738         List<Contributor> contributors = project.getContributors();
739         writeContributors( writer, contributors );
740 
741         // Extra DOAP
742         Map<Object, String> map = doapOptions.getExtra();
743         writeExtra( writer, project, "Extra DOAP vocabulary.", map, doapOptions.getXmlnsPrefix() );
744 
745         // ASFext
746         writeASFext( writer, project );
747 
748         // Extra extensions
749         writeExtensions( writer );
750 
751         writer.endElement(); // Project
752 
753         writeOrganizations( writer );
754 
755         writer.endElement(); // rdf:RDF
756 
757         try
758         {
759             w.close();
760         }
761         catch ( IOException e )
762         {
763             throw new MojoExecutionException( "Error when closing the writer.", e );
764         }
765 
766         if ( !messages.getWarnMessages().isEmpty() )
767         {
768             for ( String warn : messages.getWarnMessages() )
769             {
770                 getLog().warn( warn );
771             }
772         }
773 
774         if ( !messages.getErrorMessages().isEmpty() )
775         {
776             getLog().error( "" );
777             for ( String error : messages.getErrorMessages() )
778             {
779                 getLog().error( error );
780             }
781             getLog().error( "" );
782 
783             if ( ASFExtOptionsUtil.isASFProject( project ) )
784             {
785                 getLog().error( "For more information about the errors and possible solutions, "
786                                     + "please read the plugin documentation:" );
787                 getLog().error( "http://maven.apache.org/plugins/maven-doap-plugin/usage.html#DOAP_ASF_Configuration" );
788                 throw new MojoExecutionException( "The generated DOAP doesn't respect ASF rules, see above." );
789             }
790         }
791 
792         if ( validate )
793         {
794             List<String> errors = DoapUtil.validate( outputFile );
795             if ( !errors.isEmpty() )
796             {
797                 getLog().error( "" );
798                 for ( String error : errors )
799                 {
800                     getLog().error( error );
801                 }
802                 getLog().error( "" );
803 
804                 throw new MojoExecutionException( "Error parsing the generated DOAP file, see above." );
805             }
806         }
807     }
808 
809     /**
810      * Write DOAP name.
811      *
812      * @param writer  not null
813      * @param project the Maven project, not null
814      * @see <a href="http://usefulinc.com/ns/doap#name">http://usefulinc.com/ns/doap#name</a>
815      */
816     private void writeName( XMLWriter writer, MavenProject project )
817     {
818         String name = DoapUtil.interpolate( doapOptions.getName(), project, settings );
819         if ( StringUtils.isEmpty( name ) )
820         {
821             messages.addMessage( new String[]{ "doapOptions", "name" }, null,
822                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
823             return;
824         }
825 
826         DoapUtil.writeComment( writer, "A name of something." );
827         if ( ASFExtOptionsUtil.isASFProject( project ) && !name.toLowerCase( Locale.ENGLISH ).startsWith( "apache" ) )
828         {
829             name = "Apache " + name;
830         }
831         DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "name", name );
832     }
833 
834     /**
835      * Write DOAP description.
836      *
837      * @param writer  not null
838      * @param project the Maven project, not null
839      * @see <a href="http://usefulinc.com/ns/doap#description">http://usefulinc.com/ns/doap#description</a>
840      * @see <a href="http://usefulinc.com/ns/doap#shortdesc">http://usefulinc.com/ns/doap#shortdesc</a>
841      */
842     private void writeDescription( XMLWriter writer, MavenProject project )
843     {
844         boolean addComment = false;
845         String description = DoapUtil.interpolate( doapOptions.getDescription(), project, settings );
846         if ( StringUtils.isEmpty( description ) )
847         {
848             messages.addMessage( new String[]{ "doapOptions", "description" }, null,
849                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
850         }
851         else
852         {
853             DoapUtil.writeComment( writer, "Plain text description of a project, of 2-4 sentences in length." );
854             addComment = true;
855             DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "description", description, lang );
856         }
857 
858         String comment = "Short plain text description of a project.";
859         String shortdesc = DoapUtil.interpolate( doapOptions.getShortdesc(), project, settings );
860         if ( StringUtils.isEmpty( shortdesc ) )
861         {
862             messages.addMessage( new String[]{ "doapOptions", "shortdesc" }, null,
863                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
864             return;
865         }
866         if ( description.equals( shortdesc ) )
867         {
868             // try to get the first 10 words of the description
869             String sentence = StringUtils.split( shortdesc, "." )[0];
870             if ( StringUtils.split( sentence, " " ).length > 10 )
871             {
872                 messages.addMessage( new String[]{ "doapOptions", "shortdesc" }, null,
873                                      UserMessages.SHORT_DESC_TOO_LONG );
874                 return;
875             }
876             if ( !addComment )
877             {
878                 DoapUtil.writeComment( writer, comment );
879             }
880             DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "shortdesc", sentence, lang );
881             return;
882         }
883         if ( !addComment )
884         {
885             DoapUtil.writeComment( writer, comment );
886         }
887         DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "shortdesc", shortdesc, lang );
888     }
889 
890     /**
891      * Write DOAP created.
892      *
893      * @param writer  not null
894      * @param project the Maven project, not null
895      * @see <a href="http://usefulinc.com/ns/doap#created">http://usefulinc.com/ns/doap#created</a>
896      */
897     private void writeCreated( XMLWriter writer, MavenProject project )
898     {
899         String created = DoapUtil.interpolate( doapOptions.getCreated(), project, settings );
900         if ( StringUtils.isEmpty( created ) )
901         {
902             messages.addMessage( new String[]{ "doapOptions", "created" }, null,
903                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
904             return;
905         }
906 
907         try
908         {
909             DOAP_DATE_FORMAT.parse( created );
910         }
911         catch ( ParseException e )
912         {
913             messages.addMessage( new String[]{ "doapOptions", "created" }, null, UserMessages.INVALID_DATE );
914             return;
915         }
916 
917         DoapUtil.writeComment( writer, "Date when something was created, in YYYY-MM-DD form. e.g. 2004-04-05" );
918         DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "created", created );
919     }
920 
921     /**
922      * Write DOAP homepage and old-homepage.
923      *
924      * @param writer  not null
925      * @param project the Maven project, not null
926      * @see <a href="http://usefulinc.com/ns/doap#homepage">http://usefulinc.com/ns/doap#homepage</a>
927      * @see <a href="http://usefulinc.com/ns/doap#old-homepage">http://usefulinc.com/ns/doap#old-homepage</a>
928      */
929     private void writeHomepage( XMLWriter writer, MavenProject project )
930     {
931         String homepage = DoapUtil.interpolate( doapOptions.getHomepage(), project, settings );
932         if ( StringUtils.isEmpty( homepage ) )
933         {
934             messages.addMessage( new String[]{ "doapOptions", "homepage" }, null,
935                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
936         }
937         else
938         {
939             try
940             {
941                 new URL( homepage );
942 
943                 DoapUtil.writeComment( writer, "URL of a project's homepage, associated with exactly one project." );
944                 DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "homepage", homepage );
945             }
946             catch ( MalformedURLException e )
947             {
948                 messages.addMessage( new String[]{ "doapOptions", "homepage" }, homepage, UserMessages.INVALID_URL );
949             }
950         }
951 
952         if ( StringUtils.isNotEmpty( doapOptions.getOldHomepage() ) )
953         {
954             String oldHomepage = DoapUtil.interpolate( doapOptions.getOldHomepage(), project, settings );
955             if ( StringUtils.isEmpty( oldHomepage ) )
956             {
957                 return;
958             }
959 
960             try
961             {
962                 new URL( oldHomepage );
963 
964                 DoapUtil.writeComment( writer,
965                                        "URL of a project's past homepage, associated with exactly one project." );
966                 DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "old-homepage", oldHomepage );
967             }
968             catch ( MalformedURLException e )
969             {
970                 messages.addMessage( new String[]{ "doapOptions", "oldHomepage" }, oldHomepage,
971                                      UserMessages.INVALID_URL );
972             }
973         }
974     }
975 
976     /**
977      * Write DOAP programming-language.
978      *
979      * @param writer  not null
980      * @param project the Maven project, not null
981      * @see <a href="http://usefulinc.com/ns/doap#programming-language">
982      *      http://usefulinc.com/ns/doap#programming-language</a>
983      */
984     private void writeProgrammingLanguage( XMLWriter writer, MavenProject project )
985     {
986         if ( StringUtils.isEmpty( doapOptions.getProgrammingLanguage() ) && StringUtils.isEmpty( language ) )
987         {
988             messages.addMessage( new String[]{ "doapOptions", "programmingLanguage" }, null,
989                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
990             return;
991         }
992 
993         boolean addComment = false;
994         String comment = "Programming language.";
995         if ( StringUtils.isNotEmpty( language ) ) // backward compatible
996         {
997             getLog().warn( "The <language/> parameter is deprecated, please use " + messages.toConfiguration(
998                 new String[]{ "doapOptions", "programmingLanguage" }, null ) + " parameter instead of." );
999 
1000             language = language.trim();
1001 
1002             if ( asfExtOptions.isIncluded() )
1003             {
1004                 String asfLanguage = ASFExtOptionsUtil.getProgrammingLanguageSupportedByASF( language );
1005                 if ( asfLanguage == null )
1006                 {
1007                     messages.getErrorMessages().add(
1008                         "The deprecated " + messages.toConfiguration( new String[]{ "language" }, language )
1009                             + " parameter is not supported by ASF. Should be one of " + Arrays.toString(
1010                             ASFExtOptionsUtil.PROGRAMMING_LANGUAGES ) );
1011                 }
1012                 else
1013                 {
1014                     DoapUtil.writeComment( writer, comment );
1015                     addComment = true;
1016                     DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "programming-language",
1017                                            asfLanguage.trim() );
1018                 }
1019             }
1020             else
1021             {
1022                 DoapUtil.writeComment( writer, comment );
1023                 addComment = true;
1024                 DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "programming-language", language.trim() );
1025             }
1026         }
1027 
1028         if ( StringUtils.isNotEmpty( doapOptions.getProgrammingLanguage() ) )
1029         {
1030             String[] languages = StringUtils.split( doapOptions.getProgrammingLanguage(), "," );
1031             for ( String language : languages )
1032             {
1033                 language = language.trim();
1034 
1035                 if ( asfExtOptions.isIncluded() )
1036                 {
1037                     String asfLanguage = ASFExtOptionsUtil.getProgrammingLanguageSupportedByASF( language );
1038                     if ( asfLanguage == null )
1039                     {
1040                         messages.getErrorMessages().add(
1041                             "The " + messages.toConfiguration( new String[]{ "doapOptions", "programmingLanguage" },
1042                                                                language ) + " parameter is not supported by ASF. "
1043                                 + "Should be one of " + Arrays.toString( ASFExtOptionsUtil.PROGRAMMING_LANGUAGES ) );
1044                     }
1045                     else
1046                     {
1047                         if ( !addComment )
1048                         {
1049                             DoapUtil.writeComment( writer, comment );
1050                             addComment = true;
1051                         }
1052                         DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "programming-language",
1053                                                asfLanguage );
1054                     }
1055                 }
1056                 else
1057                 {
1058                     if ( !addComment )
1059                     {
1060                         DoapUtil.writeComment( writer, comment );
1061                         addComment = true;
1062                     }
1063                     DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "programming-language", language );
1064                 }
1065             }
1066         }
1067     }
1068 
1069     /**
1070      * Write DOAP category.
1071      *
1072      * @param writer  not null
1073      * @param project the Maven project, not null
1074      * @see <a href="http://usefulinc.com/ns/doap#category">http://usefulinc.com/ns/doap#category</a>
1075      */
1076     private void writeCategory( XMLWriter writer, MavenProject project )
1077     {
1078         if ( StringUtils.isEmpty( doapOptions.getCategory() ) && StringUtils.isEmpty( category ) )
1079         {
1080             messages.addMessage( new String[]{ "doapOptions", "category" }, null,
1081                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
1082             return;
1083         }
1084 
1085         // TODO: how to lookup category, map it, or just declare it.
1086         boolean addComment = false;
1087         String comment = "A category of project.";
1088         if ( StringUtils.isNotEmpty( category ) ) // backward compatible
1089         {
1090             getLog().warn( "The <category/> parameter is deprecated, please use " + messages.toConfiguration(
1091                 new String[]{ "doapOptions", "category" }, null ) + " parameter instead of." );
1092 
1093             category = category.trim();
1094 
1095             if ( asfExtOptions.isIncluded() )
1096             {
1097                 String asfCategory = ASFExtOptionsUtil.getCategorySupportedByASF( category );
1098                 if ( asfCategory == null )
1099                 {
1100                     messages.getErrorMessages().add(
1101                         "The deprecated " + messages.toConfiguration( new String[]{ "category" }, category )
1102                             + " parameter is not supported by ASF. Should be one of " + Arrays.toString(
1103                             ASFExtOptionsUtil.CATEGORIES ) );
1104                 }
1105                 else
1106                 {
1107                     DoapUtil.writeComment( writer, comment );
1108                     addComment = true;
1109                     DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "category",
1110                                                       ASFExtOptionsUtil.CATEGORY_RESOURCE + asfCategory );
1111                 }
1112             }
1113             else
1114             {
1115                 DoapUtil.writeComment( writer, comment );
1116                 addComment = true;
1117                 DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "category", category );
1118             }
1119         }
1120 
1121         if ( StringUtils.isNotEmpty( doapOptions.getCategory() ) )
1122         {
1123             String[] categories = StringUtils.split( doapOptions.getCategory(), "," );
1124             for ( String category : categories )
1125             {
1126                 category = category.trim();
1127 
1128                 if ( asfExtOptions.isIncluded() )
1129                 {
1130                     String asfCategory = ASFExtOptionsUtil.getCategorySupportedByASF( category );
1131                     if ( asfCategory == null )
1132                     {
1133                         messages.getErrorMessages().add(
1134                             "The " + messages.toConfiguration( new String[]{ "doapOptions", "category" }, category )
1135                                 + " parameter is not supported by ASF. Should be one of " + Arrays.toString(
1136                                 ASFExtOptionsUtil.CATEGORIES ) );
1137                     }
1138                     else
1139                     {
1140                         if ( !addComment )
1141                         {
1142                             DoapUtil.writeComment( writer, comment );
1143                             addComment = true;
1144                         }
1145                         DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "category",
1146                                                           ASFExtOptionsUtil.CATEGORY_RESOURCE + asfCategory );
1147                     }
1148                 }
1149                 else
1150                 {
1151                     if ( !addComment )
1152                     {
1153                         DoapUtil.writeComment( writer, comment );
1154                         addComment = true;
1155                     }
1156                     DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "category", category );
1157                 }
1158             }
1159         }
1160     }
1161 
1162     /**
1163      * Write DOAP download-page and download-mirror.
1164      *
1165      * @param writer  not null
1166      * @param project the Maven project, not null
1167      * @see <a href="http://usefulinc.com/ns/doap#download-page">http://usefulinc.com/ns/doap#download-page</a>
1168      * @see <a href="http://usefulinc.com/ns/doap#download-mirror">http://usefulinc.com/ns/doap#download-mirror</a>
1169      */
1170     private void writeDownloadPage( XMLWriter writer, MavenProject project )
1171     {
1172         String downloadPage = DoapUtil.interpolate( doapOptions.getDownloadPage(), project, settings );
1173         if ( StringUtils.isEmpty( downloadPage ) )
1174         {
1175             messages.addMessage( new String[]{ "doapOptions", "downloadPage" }, null,
1176                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
1177             return;
1178         }
1179 
1180         try
1181         {
1182             new URL( downloadPage );
1183 
1184             DoapUtil.writeComment( writer, "Download page." );
1185             DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "download-page", downloadPage );
1186         }
1187         catch ( MalformedURLException e )
1188         {
1189             messages.addMessage( new String[]{ "doapOptions", "downloadPage" }, downloadPage,
1190                                  UserMessages.INVALID_URL );
1191         }
1192 
1193         if ( StringUtils.isNotEmpty( doapOptions.getDownloadMirror() ) )
1194         {
1195             boolean addComment = false;
1196             String[] downloadMirrors = StringUtils.split( doapOptions.getDownloadMirror(), "," );
1197             for ( String downloadMirror : downloadMirrors )
1198             {
1199                 downloadMirror = downloadMirror.trim();
1200 
1201                 try
1202                 {
1203                     new URL( downloadMirror );
1204 
1205                     if ( !addComment )
1206                     {
1207                         DoapUtil.writeComment( writer, "Mirror of software download web page." );
1208                         addComment = true;
1209                     }
1210                     DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "download-mirror",
1211                                                       downloadMirror );
1212                 }
1213                 catch ( MalformedURLException e )
1214                 {
1215                     messages.addMessage( new String[]{ "doapOptions", "downloadMirror" }, downloadMirror,
1216                                          UserMessages.INVALID_URL );
1217                 }
1218             }
1219         }
1220     }
1221 
1222     /**
1223      * Write DOAP OS.
1224      *
1225      * @param writer  not null
1226      * @param project the Maven project, not null
1227      * @see <a href="http://usefulinc.com/ns/doap#os">http://usefulinc.com/ns/doap#os</a>
1228      */
1229     private void writeOS( XMLWriter writer, MavenProject project )
1230     {
1231         String osList = DoapUtil.interpolate( doapOptions.getOs(), project, settings );
1232         if ( StringUtils.isEmpty( osList ) )
1233         {
1234             return;
1235         }
1236 
1237         DoapUtil.writeComment( writer, "Operating system that a project is limited to." );
1238         String[] oses = StringUtils.split( osList, "," );
1239         for ( String os : oses )
1240         {
1241             DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "os", os.trim() );
1242         }
1243     }
1244 
1245     /**
1246      * Write DOAP screenshots.
1247      *
1248      * @param writer  not null
1249      * @param project the Maven project, not null
1250      * @see <a href="http://usefulinc.com/ns/doap#screenshots">http://usefulinc.com/ns/doap#screenshots</a>
1251      */
1252     private void writeScreenshots( XMLWriter writer, MavenProject project )
1253     {
1254         String screenshots = DoapUtil.interpolate( doapOptions.getScreenshots(), project, settings );
1255         if ( StringUtils.isEmpty( screenshots ) )
1256         {
1257             return;
1258         }
1259 
1260         screenshots = screenshots.trim();
1261         try
1262         {
1263             new URL( screenshots );
1264         }
1265         catch ( MalformedURLException e )
1266         {
1267             messages.addMessage( new String[]{ "doapOptions", "screenshots" }, screenshots, UserMessages.INVALID_URL );
1268             return;
1269         }
1270 
1271         DoapUtil.writeComment( writer, "Web page with screenshots of project." );
1272         DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "screenshots", screenshots );
1273     }
1274 
1275     /**
1276      * Write DOAP wiki.
1277      *
1278      * @param writer  not null
1279      * @param project the Maven project, not null
1280      * @see <a href="http://usefulinc.com/ns/doap#wiki">http://usefulinc.com/ns/doap#wiki</a>
1281      */
1282     private void writeWiki( XMLWriter writer, MavenProject project )
1283     {
1284         String wiki = DoapUtil.interpolate( doapOptions.getWiki(), project, settings );
1285         if ( StringUtils.isEmpty( wiki ) )
1286         {
1287             return;
1288         }
1289 
1290         wiki = wiki.trim();
1291         try
1292         {
1293             new URL( wiki );
1294         }
1295         catch ( MalformedURLException e )
1296         {
1297             messages.addMessage( new String[]{ "doapOptions", "wiki" }, wiki, UserMessages.INVALID_URL );
1298             return;
1299         }
1300 
1301         DoapUtil.writeComment( writer, "URL of Wiki for collaborative discussion of project." );
1302         DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "wiki", wiki );
1303     }
1304 
1305     /**
1306      * Write DOAP licenses.
1307      *
1308      * @param writer  not null
1309      * @param project the Maven project, not null
1310      * @see <a href="http://usefulinc.com/ns/doap#license">http://usefulinc.com/ns/doap#license</a>
1311      */
1312     private void writeLicenses( XMLWriter writer, MavenProject project )
1313     {
1314         String license = DoapUtil.interpolate( doapOptions.getLicense(), project, settings );
1315         if ( StringUtils.isEmpty( license ) )
1316         {
1317             boolean added = false;
1318             @SuppressWarnings( "unchecked" ) List<License> licenses = project.getLicenses();
1319             if ( licenses.size() > 1 )
1320             {
1321                 for ( int i = 1; i < licenses.size(); i++ )
1322                 {
1323                     if ( StringUtils.isEmpty( licenses.get( i ).getUrl() ) )
1324                     {
1325                         continue;
1326                     }
1327 
1328                     String licenseUrl = licenses.get( i ).getUrl().trim();
1329                     try
1330                     {
1331                         new URL( licenseUrl );
1332 
1333                         DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "license", licenseUrl );
1334                         added = true;
1335                     }
1336                     catch ( MalformedURLException e )
1337                     {
1338                         messages.addMessage( new String[]{ "project", "licenses", "license", "url" }, licenseUrl,
1339                                              UserMessages.INVALID_URL );
1340                     }
1341                 }
1342             }
1343 
1344             if ( !added )
1345             {
1346                 messages.addMessage( new String[]{ "doapOptions", "license" }, null,
1347                                      UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
1348             }
1349             return;
1350         }
1351 
1352         try
1353         {
1354             new URL( license );
1355 
1356             DoapUtil.writeComment( writer, "The URI of the license the software is distributed under." );
1357             DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "license", license );
1358         }
1359         catch ( MalformedURLException e )
1360         {
1361             messages.addMessage( new String[]{ "doapOptions", "license" }, license, UserMessages.INVALID_URL );
1362         }
1363     }
1364 
1365     /**
1366      * Write DOAP bug-database.
1367      *
1368      * @param writer  not null
1369      * @param project the Maven project, not null
1370      * @see <a href="http://usefulinc.com/ns/doap#bug-database">http://usefulinc.com/ns/doap#bug-database</a>
1371      */
1372     private void writeBugDatabase( XMLWriter writer, MavenProject project )
1373     {
1374         String bugDatabase = DoapUtil.interpolate( doapOptions.getBugDatabase(), project, settings );
1375         if ( StringUtils.isEmpty( bugDatabase ) )
1376         {
1377             messages.addMessage( new String[]{ "doapOptions", "bugDatabase" }, null,
1378                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
1379             return;
1380         }
1381 
1382         try
1383         {
1384             new URL( bugDatabase );
1385 
1386             DoapUtil.writeComment( writer, "Bug database." );
1387             DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "bug-database", bugDatabase );
1388         }
1389         catch ( MalformedURLException e )
1390         {
1391             messages.addMessage( new String[]{ "doapOptions", "bugDatabase" }, bugDatabase, UserMessages.INVALID_URL );
1392         }
1393     }
1394 
1395     /**
1396      * Write DOAP mailing-list.
1397      *
1398      * @param writer  not null
1399      * @param project the Maven project, not null
1400      * @see <a href="http://usefulinc.com/ns/doap#mailing-list">http://usefulinc.com/ns/doap#mailing-list</a>
1401      * @see DoapOptions#getMailingList()
1402      */
1403     private void writeMailingList( XMLWriter writer, MavenProject project )
1404     {
1405         String ml = DoapUtil.interpolate( doapOptions.getMailingList(), project, settings );
1406         if ( StringUtils.isEmpty( ml ) )
1407         {
1408             messages.addMessage( new String[]{ "doapOptions", "mailingList" }, null,
1409                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
1410             return;
1411         }
1412 
1413         try
1414         {
1415             new URL( ml );
1416 
1417             DoapUtil.writeComment( writer, "Mailing lists." );
1418             DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "mailing-list", ml );
1419         }
1420         catch ( MalformedURLException e )
1421         {
1422             messages.addMessage( new String[]{ "doapOptions", "mailingList" }, ml, UserMessages.INVALID_URL );
1423         }
1424     }
1425 
1426     /**
1427      * Write all DOAP releases.
1428      *
1429      * @param writer  not null
1430      * @param project the Maven project, not null
1431      * @throws MojoExecutionException if any
1432      * @see <a href="http://usefulinc.com/ns/doap#release">http://usefulinc.com/ns/doap#release</a>
1433      * @see <a href="http://usefulinc.com/ns/doap#Version">http://usefulinc.com/ns/doap#Version</a>
1434      */
1435     private void writeReleases( XMLWriter writer, MavenProject project )
1436         throws MojoExecutionException
1437     {
1438         Artifact artifact =
1439             artifactFactory.createArtifact( project.getGroupId(), project.getArtifactId(), project.getVersion(), null,
1440                                             project.getPackaging() );
1441         RepositoryMetadata metadata = new ArtifactRepositoryMetadata( artifact );
1442 
1443         for ( ArtifactRepository repo : remoteRepositories )
1444         {
1445             if ( repo.isBlacklisted() )
1446             {
1447                 continue;
1448             }
1449             if ( repo.getSnapshots().isEnabled() )
1450             {
1451                 continue;
1452             }
1453             if ( repo.getReleases().isEnabled() )
1454             {
1455                 try
1456                 {
1457                     repositoryMetadataManager.resolveAlways( metadata, localRepository, repo );
1458                     break;
1459                 }
1460                 catch ( RepositoryMetadataResolutionException e )
1461                 {
1462                     throw new MojoExecutionException(
1463                         metadata.extendedToString() + " could not be retrieved from repositories due to an error: "
1464                             + e.getMessage(), e );
1465                 }
1466             }
1467         }
1468 
1469         if ( metadata.getMetadata().getVersioning() == null )
1470         {
1471             messages.getWarnMessages().add(
1472                 "No versioning was found for " + artifact.getGroupId() + ":" + artifact.getArtifactId()
1473                     + ". Ignored DOAP <release/> tag." );
1474             return;
1475         }
1476 
1477         List<String> versions = metadata.getMetadata().getVersioning().getVersions();
1478 
1479         // Recent releases in first
1480         Collections.reverse( versions );
1481         boolean addComment = false;
1482         int i = 0;
1483         for ( String version : versions )
1484         {
1485             if ( !addComment )
1486             {
1487                 DoapUtil.writeComment( writer, "Project releases." );
1488                 addComment = true;
1489             }
1490 
1491             DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "release" );
1492             DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "Version" );
1493 
1494             DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "name" );
1495             if ( version.equals( metadata.getMetadata().getVersioning().getRelease() ) )
1496             {
1497                 writer.writeText( "Latest stable release" );
1498             }
1499             else
1500             {
1501                 writer.writeText( project.getName() + " - " + version );
1502             }
1503             writer.endElement(); // name
1504 
1505             DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "revision", version );
1506 
1507             // list all file release from all remote repos
1508             for ( ArtifactRepository repo : remoteRepositories )
1509             {
1510                 Artifact artifactRelease =
1511                     artifactFactory.createArtifact( project.getGroupId(), project.getArtifactId(), version, null,
1512                                                     project.getPackaging() );
1513 
1514                 if ( artifactRelease == null )
1515                 {
1516                     continue;
1517                 }
1518 
1519                 String fileRelease = repo.getUrl() + "/" + repo.pathOf( artifactRelease );
1520                 try
1521                 {
1522                     DoapUtil.fetchURL( settings, new URL( fileRelease ) );
1523                 }
1524                 catch ( IOException e )
1525                 {
1526                     getLog().debug( "IOException :" + e.getMessage() );
1527                     continue;
1528                 }
1529                 DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "file-release", fileRelease );
1530 
1531                 Date releaseDate = null;
1532                 try
1533                 {
1534                     releaseDate =
1535                         REPOSITORY_DATE_FORMAT.parse( metadata.getMetadata().getVersioning().getLastUpdated() );
1536                 }
1537                 catch ( ParseException e )
1538                 {
1539                     getLog().error(
1540                         "Unable to parse date '" + metadata.getMetadata().getVersioning().getLastUpdated() + "'" );
1541                     continue;
1542                 }
1543 
1544                 // See MDOAP-11
1545                 if ( i == 0 )
1546                 {
1547                     DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "created",
1548                                            DOAP_DATE_FORMAT.format( releaseDate ) );
1549                 }
1550             }
1551 
1552             writer.endElement(); // Version
1553             writer.endElement(); // release
1554 
1555             i++;
1556         }
1557     }
1558 
1559     /**
1560      * Write all DOAP repositories.
1561      *
1562      * @param writer  not null
1563      * @param project the Maven project, not null
1564      * @see <a href="http://usefulinc.com/ns/doap#Repository">http://usefulinc.com/ns/doap#Repository</a>
1565      * @see <a href="http://usefulinc.com/ns/doap#CVSRepository">http://usefulinc.com/ns/doap#CVSRepository</a>
1566      * @see <a href="http://usefulinc.com/ns/doap#SVNRepository">http://usefulinc.com/ns/doap#SVNRepository</a>
1567      */
1568     private void writeSourceRepositories( XMLWriter writer, MavenProject project )
1569     {
1570         String anonymousConnection = DoapUtil.interpolate( doapOptions.getScmAnonymous(), project, settings );
1571         if ( StringUtils.isEmpty( anonymousConnection ) )
1572         {
1573             messages.addMessage( new String[]{ "doapOptions", "scmAnonymousConnection" }, null,
1574                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
1575         }
1576         else
1577         {
1578             DoapUtil.writeComment( writer, "Anonymous Source Repository." );
1579 
1580             try
1581             {
1582                 new URL( anonymousConnection );
1583 
1584                 DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "repository" );
1585                 DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "Repository" );
1586                 DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "location",
1587                                                   anonymousConnection );
1588                 writer.endElement(); // Repository
1589                 writer.endElement(); // repository
1590             }
1591             catch ( MalformedURLException e )
1592             {
1593                 writeSourceRepository( writer, project, anonymousConnection );
1594             }
1595         }
1596 
1597         String devConnection = DoapUtil.interpolate( doapOptions.getScmDeveloper(), project, settings );
1598         if ( StringUtils.isEmpty( devConnection ) )
1599         {
1600             messages.addMessage( new String[]{ "doapOptions", "scmDeveloperConnection" }, null,
1601                                  UserMessages.REQUIRED_BY_ASF_OR_RECOMMENDED );
1602         }
1603         else
1604         {
1605             DoapUtil.writeComment( writer, "Developer Source Repository." );
1606 
1607             try
1608             {
1609                 new URL( devConnection );
1610 
1611                 DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "repository" );
1612                 DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "Repository" );
1613                 DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "location", devConnection );
1614                 writer.endElement(); // Repository
1615                 writer.endElement(); // repository
1616             }
1617             catch ( MalformedURLException e )
1618             {
1619                 writeSourceRepository( writer, project, devConnection );
1620             }
1621         }
1622     }
1623 
1624     /**
1625      * Write a DOAP repository, for instance:
1626      * <p/>
1627      * <pre>
1628      *   &lt;repository&gt;
1629      *     &lt;SVNRepository&gt;
1630      *       &lt;location rdf:resource="http://svn.apache.org/repos/asf/maven/components/trunk/"/&gt;
1631      *       &lt;browse rdf:resource="http://svn.apache.org/viewcvs.cgi/maven/components/trunk/"/&gt;
1632      *     &lt;/SVNRepository&gt;
1633      *   &lt;/repository&gt;
1634      * </pre>
1635      *
1636      * @param writer     not null
1637      * @param project    the Maven project, not null
1638      * @param connection not null
1639      * @see <a href="http://usefulinc.com/ns/doap#Repository">http://usefulinc.com/ns/doap#Repository</a>
1640      * @see <a href="http://usefulinc.com/ns/doap#CVSRepository">http://usefulinc.com/ns/doap#CVSRepository</a>
1641      * @see <a href="http://usefulinc.com/ns/doap#SVNRepository">http://usefulinc.com/ns/doap#SVNRepository</a>
1642      */
1643     private void writeSourceRepository( XMLWriter writer, MavenProject project, String connection )
1644     {
1645         ScmRepository repository = getScmRepository( connection );
1646 
1647         DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "repository" );
1648 
1649         if ( isScmSystem( repository, "cvs" ) )
1650         {
1651             DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "CVSRepository" );
1652 
1653             CvsScmProviderRepository cvsRepo = (CvsScmProviderRepository) repository.getProviderRepository();
1654 
1655             DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "anon-root", cvsRepo.getCvsRoot() );
1656             DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "module", cvsRepo.getModule() );
1657         }
1658         else if ( isScmSystem( repository, "svn" ) )
1659         {
1660             DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "SVNRepository" );
1661 
1662             SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository();
1663 
1664             DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "location", svnRepo.getUrl() );
1665         }
1666         else
1667         {
1668             /*
1669              * Supported DOAP repositories actually unsupported by SCM: BitKeeper
1670              * (http://usefulinc.com/ns/doap#BKRepository) Arch (http://usefulinc.com/ns/doap#ArchRepository) Other SCM
1671              * repos are unsupported by DOAP.
1672              */
1673             DoapUtil.writeStartElement( writer, doapOptions.getXmlnsPrefix(), "Repository" );
1674 
1675             if ( connection.length() < 4 )
1676             {
1677                 throw new IllegalArgumentException( "The source repository connection is too short." );
1678             }
1679 
1680             DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "location",
1681                                               connection.substring( 4 ) );
1682         }
1683 
1684         DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "browse", project.getScm().getUrl() );
1685 
1686         writer.endElement(); // CVSRepository || SVNRepository || Repository
1687         writer.endElement(); // repository
1688     }
1689 
1690     /**
1691      * Write all DOAP persons.
1692      *
1693      * @param writer       not null
1694      * @param contributors list of developers or contributors
1695      */
1696     private void writeContributors( XMLWriter writer, List<Contributor> contributors )
1697     {
1698         if ( contributors == null || contributors.isEmpty() )
1699         {
1700             return;
1701         }
1702 
1703         boolean isDeveloper = Developer.class.isAssignableFrom( contributors.get( 0 ).getClass() );
1704         if ( isDeveloper )
1705         {
1706             DoapUtil.writeComment( writer, "Main committers." );
1707         }
1708         else
1709         {
1710             DoapUtil.writeComment( writer, "Contributed persons." );
1711         }
1712 
1713         List<Contributor> maintainers = DoapUtil.getContributorsWithMaintainerRole( i18n, contributors );
1714         List<Contributor> developers = DoapUtil.getContributorsWithDeveloperRole( i18n, contributors );
1715         List<Contributor> documenters = DoapUtil.getContributorsWithDocumenterRole( i18n, contributors );
1716         List<Contributor> translators = DoapUtil.getContributorsWithTranslatorRole( i18n, contributors );
1717         List<Contributor> testers = DoapUtil.getContributorsWithTesterRole( i18n, contributors );
1718         List<Contributor> helpers = DoapUtil.getContributorsWithHelperRole( i18n, contributors );
1719         List<Contributor> unknowns = DoapUtil.getContributorsWithUnknownRole( i18n, contributors );
1720 
1721         // By default, all developers are maintainers and contributors are helpers
1722         if ( isDeveloper )
1723         {
1724             maintainers.addAll( unknowns );
1725         }
1726         else
1727         {
1728             helpers.addAll( unknowns );
1729         }
1730 
1731         // all alphabetical
1732         if ( developers.size() != 0 )
1733         {
1734             writeContributor( writer, developers, "developer" );
1735         }
1736         if ( documenters.size() != 0 )
1737         {
1738             writeContributor( writer, documenters, "documenter" );
1739         }
1740         if ( helpers.size() != 0 )
1741         {
1742             writeContributor( writer, helpers, "helper" );
1743         }
1744         if ( maintainers.size() != 0 )
1745         {
1746             writeContributor( writer, maintainers, "maintainer" );
1747         }
1748         if ( testers.size() != 0 )
1749         {
1750             writeContributor( writer, testers, "tester" );
1751         }
1752         if ( translators.size() != 0 )
1753         {
1754             writeContributor( writer, translators, "translator" );
1755         }
1756     }
1757 
1758     /**
1759      * Write a DOAP maintainer or developer or documenter or translator or tester or helper, for instance:
1760      * <p/>
1761      * <pre>
1762      *   &lt;maintainer&gt;
1763      *     &lt;foaf:Person&gt;
1764      *       &lt;foaf:name&gt;Emmanuel Venisse&lt;/foaf:name&gt;
1765      *       &lt;foaf:mbox rdf:resource="mailto:evenisse@apache.org"/&gt;
1766      *     &lt;/foaf:Person&gt;
1767      *   &lt;/maintainer&gt;
1768      * </pre>
1769      *
1770      * @param writer                   not null
1771      * @param developersOrContributors list of <code>{@link Developer}/{@link Contributor}</code>
1772      * @param doapType                 not null
1773      * @see #writeContributor(XMLWriter, Object, String)
1774      */
1775     private void writeContributor( XMLWriter writer, List<Contributor> developersOrContributors, String doapType )
1776     {
1777         if ( developersOrContributors == null || developersOrContributors.isEmpty() )
1778         {
1779             return;
1780         }
1781 
1782         sortContributors( developersOrContributors );
1783 
1784         for ( Contributor developersOrContributor : developersOrContributors )
1785         {
1786             writeContributor( writer, developersOrContributor, doapOptions.getXmlnsPrefix(), doapType );
1787         }
1788     }
1789 
1790     /**
1791      * Writer a single developer or contributor
1792      *
1793      * @param writer                 not null
1794      * @param xmlsPrefix             could be null
1795      * @param developerOrContributor not null, instance of <code>{@link Developer}/{@link Contributor}</code>
1796      * @param doapType               not null
1797      * @see <a href="http://usefulinc.com/ns/doap#maintainer">http://usefulinc.com/ns/doap#maintainer</a>
1798      * @see <a href="http://usefulinc.com/ns/doap#developer">http://usefulinc.com/ns/doap#developer</a>
1799      * @see <a href="http://usefulinc.com/ns/doap#documenter">http://usefulinc.com/ns/doap#documenter</a>
1800      * @see <a href="http://usefulinc.com/ns/doap#translator">http://usefulinc.com/ns/doap#translator</a>
1801      * @see <a href="http://usefulinc.com/ns/doap#tester">http://usefulinc.com/ns/doap#tester</a>
1802      * @see <a href="http://usefulinc.com/ns/doap#helper">http://usefulinc.com/ns/doap#helper</a>
1803      * @see <a href="http://xmlns.com/foaf/0.1/Person">http://xmlns.com/foaf/0.1/Person</a>
1804      * @see <a href="http://xmlns.com/foaf/0.1/name">http://xmlns.com/foaf/0.1/name</a>
1805      * @see <a href="http://xmlns.com/foaf/0.1/mbox">http://xmlns.com/foaf/0.1/mbox</a>
1806      * @see <a href="http://xmlns.com/foaf/0.1/Organization">http://xmlns.com/foaf/0.1/Organization</a>
1807      * @see <a href="http://xmlns.com/foaf/0.1/homepage">http://xmlns.com/foaf/0.1/homepage</a>
1808      */
1809     private void writeContributor( XMLWriter writer, Contributor developerOrContributor, String xmlsPrefix,
1810                                    String doapType )
1811     {
1812         if ( developerOrContributor == null )
1813         {
1814             return;
1815         }
1816 
1817         if ( StringUtils.isEmpty( doapType ) )
1818         {
1819             throw new IllegalArgumentException( "doapType is required." );
1820         }
1821 
1822         String name = developerOrContributor.getName();
1823         String email = developerOrContributor.getEmail();
1824         String organization = developerOrContributor.getOrganization();
1825         String organizationUrl = developerOrContributor.getOrganizationUrl();
1826         String homepage = developerOrContributor.getUrl();
1827         String nodeId = null;
1828 
1829         // Name is required to write doap
1830         if ( StringUtils.isEmpty( name ) )
1831         {
1832             messages.addMessage( new String[]{ "project", "developers|contributors", "developer|contributor", "name" },
1833                                  null, UserMessages.REQUIRED );
1834             return;
1835         }
1836 
1837         if ( !StringUtils.isEmpty( organization ) || !StringUtils.isEmpty( organizationUrl ) )
1838         {
1839             DoapUtil.Organization doapOrganization = DoapUtil.addOrganization( organization, organizationUrl );
1840             nodeId = DoapUtil.getNodeId();
1841             doapOrganization.addMember( nodeId );
1842         }
1843 
1844         DoapUtil.writeStartElement( writer, xmlsPrefix, doapType );
1845         DoapUtil.writeStartElement( writer, "foaf", "Person" );
1846         if ( StringUtils.isNotEmpty( nodeId ) )
1847         {
1848             writer.addAttribute( "rdf:nodeID", nodeId );
1849         }
1850         DoapUtil.writeStartElement( writer, "foaf", "name" );
1851         writer.writeText( name );
1852         writer.endElement(); // foaf:name
1853         if ( StringUtils.isNotEmpty( email ) )
1854         {
1855             if ( DoapUtil.isValidEmail( email ) )
1856             {
1857                 DoapUtil.writeRdfResourceElement( writer, "foaf", "mbox", "mailto:" + email );
1858             }
1859             else
1860             {
1861                 messages.addMessage(
1862                     new String[]{ "project", "developers|contributors", "developer|contributor", "email" }, null,
1863                     UserMessages.INVALID_EMAIL );
1864             }
1865         }
1866         if ( StringUtils.isNotEmpty( organization ) && StringUtils.isNotEmpty( organizationUrl ) )
1867         {
1868             try
1869             {
1870                 new URL( organizationUrl );
1871 
1872                 DoapUtil.addOrganization( organization, organizationUrl );
1873             }
1874             catch ( MalformedURLException e )
1875             {
1876                 messages.addMessage(
1877                     new String[]{ "project", "developers|contributors", "developer|contributor", "organizationUrl" },
1878                     organizationUrl, UserMessages.INVALID_URL );
1879             }
1880         }
1881         if ( StringUtils.isNotEmpty( homepage ) )
1882         {
1883             try
1884             {
1885                 new URL( homepage );
1886 
1887                 DoapUtil.writeRdfResourceElement( writer, "foaf", "homepage", homepage );
1888             }
1889             catch ( MalformedURLException e )
1890             {
1891                 messages.addMessage(
1892                     new String[]{ "project", "developers|contributors", "developer|contributor", "homepage" }, homepage,
1893                     UserMessages.INVALID_URL );
1894             }
1895         }
1896         writer.endElement(); // foaf:Person
1897         writer.endElement(); // doapType
1898     }
1899 
1900     /**
1901      * Return a <code>SCM repository</code> defined by a given url
1902      *
1903      * @param scmUrl an SCM URL
1904      * @return a valid SCM repository or null
1905      */
1906     private ScmRepository getScmRepository( String scmUrl )
1907     {
1908         ScmRepository repo = null;
1909         if ( !StringUtils.isEmpty( scmUrl ) )
1910         {
1911             try
1912             {
1913                 repo = scmManager.makeScmRepository( scmUrl );
1914             }
1915             catch ( NoSuchScmProviderException e )
1916             {
1917                 if ( getLog().isDebugEnabled() )
1918                 {
1919                     getLog().debug( e.getMessage(), e );
1920                 }
1921             }
1922             catch ( ScmRepositoryException e )
1923             {
1924                 if ( getLog().isDebugEnabled() )
1925                 {
1926                     getLog().debug( e.getMessage(), e );
1927                 }
1928             }
1929         }
1930 
1931         return repo;
1932     }
1933 
1934     /**
1935      * Write the ASF extensions
1936      *
1937      * @param writer  not null
1938      * @param project the Maven project, not null
1939      * @see <a href="http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext">
1940      *      http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext</a>;
1941      * @see <a href="http://projects.apache.org/docs/pmc.html">http://projects.apache.org/docs/pmc.html</a>
1942      */
1943     private void writeASFext( XMLWriter writer, MavenProject project )
1944     {
1945         if ( !asfExtOptions.isIncluded() )
1946         {
1947             return;
1948         }
1949 
1950         DoapUtil.writeComment( writer, "ASF extension." );
1951 
1952         // asfext:pmc
1953         String pmc = DoapUtil.interpolate( asfExtOptions.getPmc(), project, settings );
1954         if ( StringUtils.isNotEmpty( pmc ) )
1955         {
1956             DoapUtil.writeRdfResourceElement( writer, asfExtOptions.getXmlnsPrefix(), "pmc", pmc );
1957         }
1958         else
1959         {
1960             messages.addMessage( new String[]{ "asfExtOptions", "pmc" }, null, UserMessages.REQUIRED_BY_ASF );
1961         }
1962 
1963         // asfext:name
1964         String name = DoapUtil.interpolate( asfExtOptions.getName(), project, settings );
1965         if ( StringUtils.isNotEmpty( name ) )
1966         {
1967             if ( !name.toLowerCase( Locale.ENGLISH ).trim().startsWith( "apache" ) )
1968             {
1969                 name = "Apache " + name;
1970             }
1971             DoapUtil.writeElement( writer, asfExtOptions.getXmlnsPrefix(), "name", name );
1972         }
1973         else
1974         {
1975             messages.addMessage( new String[]{ "asfExtOptions", "name" }, null, UserMessages.REQUIRED_BY_ASF );
1976         }
1977 
1978         String homepage = DoapUtil.interpolate( doapOptions.getHomepage(), project, settings );
1979         if ( StringUtils.isNotEmpty( homepage ) )
1980         {
1981             try
1982             {
1983                 new URL( homepage );
1984 
1985                 DoapUtil.writeRdfResourceElement( writer, "foaf", "homepage", homepage );
1986             }
1987             catch ( MalformedURLException e )
1988             {
1989                 messages.addMessage( new String[]{ "doapOptions", "homepage" }, homepage, UserMessages.INVALID_URL );
1990             }
1991         }
1992 
1993         // asfext:charter
1994         if ( StringUtils.isEmpty( asfExtOptions.getCharter() ) )
1995         {
1996             messages.addMessage( new String[]{ "asfExtOptions", "charter" }, null, UserMessages.REQUIRED_BY_ASF );
1997         }
1998         else
1999         {
2000             DoapUtil.writeElement( writer, asfExtOptions.getXmlnsPrefix(), "charter", asfExtOptions.getCharter() );
2001         }
2002 
2003         // asfext:chair
2004         @SuppressWarnings( "unchecked" ) List<Developer> developers =
2005             new ArrayList<Developer>( project.getDevelopers() );
2006         sortContributors( developers );
2007 
2008         if ( StringUtils.isNotEmpty( asfExtOptions.getChair() ) )
2009         {
2010             DoapUtil.writeStartElement( writer, asfExtOptions.getXmlnsPrefix(), "chair" );
2011             DoapUtil.writeStartElement( writer, "foaf", "Person" );
2012             DoapUtil.writeStartElement( writer, "foaf", "name" );
2013             writer.writeText( asfExtOptions.getChair() );
2014             writer.endElement(); // foaf:name
2015             writer.endElement(); // foaf:Person
2016             writer.endElement(); // asfext:chair
2017         }
2018         else
2019         {
2020             Developer chair = ASFExtOptionsUtil.findChair( developers );
2021             if ( chair != null )
2022             {
2023                 writeContributor( writer, chair, asfExtOptions.getXmlnsPrefix(), "chair" );
2024                 developers.remove( chair );
2025             }
2026             else
2027             {
2028                 messages.addMessage( new String[]{ "asfExtOptions", "chair" }, null, UserMessages.REQUIRED_BY_ASF );
2029             }
2030         }
2031 
2032         // asfext:member
2033         if ( developers != null && developers.size() > 0 )
2034         {
2035             List<Developer> pmcMembers = ASFExtOptionsUtil.findPMCMembers( developers );
2036             for ( Developer pmcMember : pmcMembers )
2037             {
2038                 writeContributor( writer, pmcMember, asfExtOptions.getXmlnsPrefix(), "member" );
2039             }
2040         }
2041 
2042         writeASFImplements( writer );
2043 
2044         Map<Object, String> map = asfExtOptions.getExtra();
2045         writeExtra( writer, project, "Extra ASFExt vocabulary.", map, asfExtOptions.getXmlnsPrefix() );
2046     }
2047 
2048     /**
2049      * Write the ASF implements.
2050      *
2051      * @param writer not null
2052      * @see <a href="http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext">
2053      *      http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext</a>;
2054      * @see <a href="http://projects.apache.org/docs/standards.html">http://projects.apache.org/docs/standards.html</a>
2055      */
2056     private void writeASFImplements( XMLWriter writer )
2057     {
2058         if ( asfExtOptions.getStandards() == null || asfExtOptions.getStandards().isEmpty() )
2059         {
2060             return;
2061         }
2062 
2063         for ( Standard standard : asfExtOptions.getStandards() )
2064         {
2065             DoapUtil.writeStartElement( writer, asfExtOptions.getXmlnsPrefix(), "implements" );
2066             DoapUtil.writeStartElement( writer, asfExtOptions.getXmlnsPrefix(), "Standard" );
2067 
2068             if ( StringUtils.isEmpty( standard.getTitle() ) )
2069             {
2070                 messages.addMessage( new String[]{ "asfExtOptions", "standards", "title" }, null,
2071                                      UserMessages.REQUIRED_BY_ASF );
2072             }
2073             else
2074             {
2075                 DoapUtil.writeElement( writer, asfExtOptions.getXmlnsPrefix(), "title", standard.getTitle().trim() );
2076             }
2077 
2078             if ( StringUtils.isEmpty( standard.getBody() ) )
2079             {
2080                 messages.addMessage( new String[]{ "asfExtOptions", "standards", "body" }, null,
2081                                      UserMessages.REQUIRED_BY_ASF );
2082             }
2083             else
2084             {
2085                 DoapUtil.writeElement( writer, asfExtOptions.getXmlnsPrefix(), "body", standard.getBody().trim() );
2086             }
2087 
2088             if ( StringUtils.isEmpty( standard.getId() ) )
2089             {
2090                 messages.addMessage( new String[]{ "asfExtOptions", "standards", "id" }, null,
2091                                      UserMessages.REQUIRED_BY_ASF );
2092             }
2093             else
2094             {
2095                 DoapUtil.writeElement( writer, asfExtOptions.getXmlnsPrefix(), "id", standard.getId().trim() );
2096             }
2097 
2098             if ( StringUtils.isNotEmpty( standard.getUrl() ) )
2099             {
2100                 String standardUrl = standard.getUrl().trim();
2101                 try
2102                 {
2103                     new URL( standardUrl );
2104 
2105                     DoapUtil.writeElement( writer, asfExtOptions.getXmlnsPrefix(), "url", standardUrl );
2106                 }
2107                 catch ( MalformedURLException e )
2108                 {
2109                     messages.addMessage( new String[]{ "asfExtOptions", "standards", "url" }, standardUrl,
2110                                          UserMessages.INVALID_URL );
2111                 }
2112             }
2113 
2114             writer.endElement(); // asfext:Standard
2115             writer.endElement(); // asfext:implements
2116         }
2117     }
2118 
2119     /**
2120      * Write a Foaf Organization, for instance:
2121      * <p/>
2122      * <pre>
2123      *   &lt;<foaf:Organization&gt;
2124      *     &lt;foaf:name&gt;YoyoDyne&lt;/foaf:name&gt;
2125      *     &lt;foaf:homepage rdf:resource="http://yoyodyne.example.org"/&gt;
2126      *     &lt;foaf:member rdf:nodeID="benny_profane"&gt;
2127      *   &lt;/foaf:Organization&gt;
2128      * </pre>
2129      *
2130      * @param writer                   not null
2131      * @param developersOrContributors list of <code>{@link Developer}/{@link Contributor}</code>
2132      * @param doapType                 not null
2133      * @see #writeContributor(XMLWriter, Object, String)
2134      */
2135     private void writeOrganizations( XMLWriter writer )
2136     {
2137         Set<Entry<String, DoapUtil.Organization>> organizations = DoapUtil.getOrganizations();
2138 
2139         for ( Entry<String, DoapUtil.Organization> organizationEntry : organizations )
2140         {
2141             DoapUtil.Organization organization = organizationEntry.getValue();
2142 
2143             DoapUtil.writeStartElement( writer, "foaf", "Organization" );
2144             if ( !StringUtils.isEmpty( organization.getName() ) )
2145             {
2146                 DoapUtil.writeElement( writer, "foaf", "name", organization.getName() );
2147             }
2148             if ( !StringUtils.isEmpty( organization.getUrl() ) )
2149             {
2150                 try
2151                 {
2152                     new URL( organization.getUrl() );
2153 
2154                     DoapUtil.writeRdfResourceElement( writer, "foaf", "homepage", organization.getUrl() );
2155                 }
2156                 catch ( MalformedURLException e )
2157                 {
2158                     messages.errorMessages.add(
2159                         "The organization URL " + organization.getUrl() + " is not a valid URL." );
2160                 }
2161             }
2162             List<String> members = organization.getMembers();
2163             for ( String member : members )
2164             {
2165                 DoapUtil.writeRdfNodeIdElement( writer, "foaf", "member", member );
2166             }
2167             writer.endElement(); // foaf:Organization
2168         }
2169     }
2170 
2171     /**
2172      * Write DOAP audience.
2173      *
2174      * @param writer not null
2175      * @see <a href="http://usefulinc.com/ns/doap#audience">http://usefulinc.com/ns/doap#audience</a>
2176      * @since 1.1
2177      */
2178     private void writeAudience( XMLWriter writer )
2179     {
2180         String audience = DoapUtil.interpolate( doapOptions.getAudience(), project, settings );
2181         if ( StringUtils.isEmpty( audience ) )
2182         {
2183             return;
2184         }
2185 
2186         DoapUtil.writeComment( writer, "Audience." );
2187         DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "audience", audience );
2188     }
2189 
2190     /**
2191      * Write DOAP blog.
2192      *
2193      * @param writer not null
2194      * @see <a href="http://usefulinc.com/ns/doap#blog">http://usefulinc.com/ns/doap#blog</a>
2195      * @since 1.1
2196      */
2197     private void writeBlog( XMLWriter writer )
2198     {
2199         String blog = DoapUtil.interpolate( doapOptions.getBlog(), project, settings );
2200         if ( StringUtils.isEmpty( doapOptions.getBlog() ) )
2201         {
2202             return;
2203         }
2204 
2205         blog = blog.trim();
2206         try
2207         {
2208             new URL( blog );
2209         }
2210         catch ( MalformedURLException e )
2211         {
2212             messages.addMessage( new String[]{ "doapOptions", "blog" }, blog, UserMessages.INVALID_URL );
2213             return;
2214         }
2215 
2216         DoapUtil.writeComment( writer, "Blog page." );
2217         DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "blog", blog );
2218     }
2219 
2220     /**
2221      * Write DOAP plateform.
2222      *
2223      * @param writer not null
2224      * @see <a href="http://usefulinc.com/ns/doap#plateform">http://usefulinc.com/ns/doap#plateform</a>
2225      * @since 1.1
2226      */
2227     private void writePlateform( XMLWriter writer )
2228     {
2229         if ( StringUtils.isEmpty( doapOptions.getPlatform() ) )
2230         {
2231             return;
2232         }
2233 
2234         DoapUtil.writeComment( writer, "Platform." );
2235         String[] platforms = StringUtils.split( doapOptions.getPlatform(), "," );
2236         for ( String platform : platforms )
2237         {
2238             DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "platform", platform.trim() );
2239         }
2240     }
2241 
2242     /**
2243      * Write DOAP vendor.
2244      *
2245      * @param writer  not null
2246      * @param project the Maven project, not null
2247      * @see <a href="http://usefulinc.com/ns/doap#vendor">http://usefulinc.com/ns/doap#vendor</a>
2248      * @since 1.1
2249      */
2250     private void writeVendor( XMLWriter writer, MavenProject project )
2251     {
2252         String vendor = DoapUtil.interpolate( doapOptions.getVendor(), project, settings );
2253         if ( StringUtils.isEmpty( vendor ) )
2254         {
2255             return;
2256         }
2257 
2258         DoapUtil.writeComment( writer, "Vendor." );
2259         DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "vendor", vendor );
2260     }
2261 
2262     /**
2263      * Write DOAP language.
2264      *
2265      * @param writer not null
2266      * @see <a href="http://usefulinc.com/ns/doap#language">http://usefulinc.com/ns/doap#language</a>
2267      * @since 1.1
2268      */
2269     private void writeLanguage( XMLWriter writer )
2270     {
2271         if ( StringUtils.isEmpty( doapOptions.getLanguage() ) )
2272         {
2273             return;
2274         }
2275 
2276         boolean addComment = false;
2277         String[] languages = StringUtils.split( doapOptions.getLanguage(), "," );
2278         for ( String language : languages )
2279         {
2280             language = language.trim();
2281 
2282             if ( Arrays.binarySearch( Locale.getISOLanguages(), language ) < 0 )
2283             {
2284                 messages.addMessage( new String[]{ "doapOptions", "languages" }, language,
2285                                      UserMessages.INVALID_ISO_DATE );
2286                 continue;
2287             }
2288 
2289             if ( !addComment )
2290             {
2291                 DoapUtil.writeComment( writer, "Language." );
2292                 addComment = true;
2293             }
2294             DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "language", language );
2295         }
2296     }
2297 
2298     /**
2299      * Write DOAP service-endpoint.
2300      *
2301      * @param writer not null
2302      * @see <a href="http://usefulinc.com/ns/doap#service-endpoint">http://usefulinc.com/ns/doap#service-endpoint</a>
2303      * @since 1.1
2304      */
2305     private void writeServiceEndpoint( XMLWriter writer )
2306     {
2307         String serviceEndpoint = DoapUtil.interpolate( doapOptions.getServiceEndpoint(), project, settings );
2308         if ( StringUtils.isEmpty( serviceEndpoint ) )
2309         {
2310             return;
2311         }
2312 
2313         serviceEndpoint = serviceEndpoint.trim();
2314         try
2315         {
2316             new URL( serviceEndpoint );
2317         }
2318         catch ( MalformedURLException e )
2319         {
2320             messages.addMessage( new String[]{ "doapOptions", "serviceEndpoint" }, serviceEndpoint,
2321                                  UserMessages.INVALID_URL );
2322             return;
2323         }
2324 
2325         DoapUtil.writeComment( writer, "Service endpoint." );
2326         DoapUtil.writeRdfResourceElement( writer, doapOptions.getXmlnsPrefix(), "service-endpoint", serviceEndpoint );
2327     }
2328 
2329     /**
2330      * Write DOAP implements.
2331      *
2332      * @param writer not null
2333      * @see <a href="http://usefulinc.com/ns/doap#implements">http://usefulinc.com/ns/doap#implements</a>
2334      * @since 1.1
2335      */
2336     private void writeImplements( XMLWriter writer )
2337     {
2338         if ( StringUtils.isEmpty( doapOptions.getImplementations() ) )
2339         {
2340             return;
2341         }
2342 
2343         DoapUtil.writeComment( writer, "Implements." );
2344         String[] implementations = StringUtils.split( doapOptions.getImplementations(), "," );
2345         for ( String implementation : implementations )
2346         {
2347             DoapUtil.writeElement( writer, doapOptions.getXmlnsPrefix(), "implements", implementation.trim() );
2348         }
2349     }
2350 
2351     /**
2352      * Write extra for DOAP or any extension.
2353      *
2354      * @param writer      not null
2355      * @param project     not null
2356      * @param comment     not null
2357      * @param map         not null
2358      * @param xmlnsPrefix not null
2359      * @since 1.1
2360      */
2361     private void writeExtra( XMLWriter writer, MavenProject project, String comment, Map<Object, String> map,
2362                              String xmlnsPrefix )
2363     {
2364         if ( map == null || map.isEmpty() )
2365         {
2366             return;
2367         }
2368 
2369         boolean addComment = false;
2370         for ( Map.Entry<Object, String> entry : map.entrySet() )
2371         {
2372             String key = (String) entry.getKey();
2373             String value = entry.getValue();
2374 
2375             if ( value == null )
2376             {
2377                 continue;
2378             }
2379 
2380             String interpolatedValue = DoapUtil.interpolate( value, project, settings );
2381             if ( interpolatedValue == null )
2382             {
2383                 continue;
2384             }
2385 
2386             if ( !addComment )
2387             {
2388                 DoapUtil.writeComment( writer, comment );
2389                 addComment = true;
2390             }
2391 
2392             try
2393             {
2394                 new URL( interpolatedValue );
2395 
2396                 DoapUtil.writeRdfResourceElement( writer, xmlnsPrefix, key, interpolatedValue );
2397             }
2398             catch ( MalformedURLException e )
2399             {
2400                 DoapUtil.writeElement( writer, xmlnsPrefix, key, interpolatedValue );
2401             }
2402         }
2403     }
2404 
2405     /**
2406      * Write the extra DOAP extensions.
2407      *
2408      * @param writer not null
2409      * @since 1.1
2410      */
2411     private void writeExtensions( XMLWriter writer )
2412     {
2413         if ( !( extOptions != null && extOptions.length > 0 && !extOptions[0].getExtensions().isEmpty() ) )
2414         {
2415             return;
2416         }
2417 
2418         for ( ExtOptions extOption : extOptions )
2419         {
2420             Map<Object, String> map = extOption.getExtensions();
2421             writeExtra( writer, project, "Other extension vocabulary.", map, extOption.getXmlnsPrefix() );
2422         }
2423     }
2424 
2425     // ----------------------------------------------------------------------
2426     // Static methods
2427     // ----------------------------------------------------------------------
2428 
2429     /**
2430      * Convenience method that return true is the defined <code>SCM repository</code> is a known provider.
2431      * <p>
2432      * Actually, we fully support Clearcase, CVS, Perforce, Starteam, SVN by the maven-scm-providers component.
2433      * </p>
2434      *
2435      * @param scmRepository a SCM repository
2436      * @param scmProvider   a SCM provider name
2437      * @return true if the provider of the given SCM repository is equal to the given scm provider.
2438      * @see <a href="http://svn.apache.org/repos/asf/maven/scm/trunk/maven-scm-providers/">maven-scm-providers</a>
2439      */
2440     private static boolean isScmSystem( ScmRepository scmRepository, String scmProvider )
2441     {
2442         if ( StringUtils.isEmpty( scmProvider ) )
2443         {
2444             return false;
2445         }
2446 
2447         if ( scmRepository != null && scmProvider.equalsIgnoreCase( scmRepository.getProvider() ) )
2448         {
2449             return true;
2450         }
2451 
2452         return false;
2453     }
2454 
2455     /**
2456      * Sort Contributor by name or Developer by id.
2457      *
2458      * @param contributors not null
2459      * @since 1.1
2460      */
2461     @SuppressWarnings( { "unchecked", "rawtypes" } )
2462     private static void sortContributors( List contributors )
2463     {
2464         Collections.sort( contributors, new Comparator<Contributor>()
2465         {
2466             public int compare( Contributor contributor1, Contributor contributor2 )
2467             {
2468                 if ( contributor1 == contributor2 )
2469                 {
2470                     return 0;
2471                 }
2472 
2473                 if ( contributor1 == null && contributor2 != null )
2474                 {
2475                     return -1;
2476                 }
2477                 if ( contributor1 != null && contributor2 == null )
2478                 {
2479                     return +1;
2480                 }
2481 
2482                 if ( Developer.class.isAssignableFrom( contributor1.getClass() ) && Developer.class.isAssignableFrom(
2483                     contributor2.getClass() ) )
2484                 {
2485                     Developer developer1 = (Developer) contributor1;
2486                     Developer developer2 = (Developer) contributor2;
2487 
2488                     if ( developer1.getId() == null && developer2.getId() != null )
2489                     {
2490                         return -1;
2491                     }
2492                     if ( developer1.getId() != null && developer2.getId() == null )
2493                     {
2494                         return +1;
2495                     }
2496 
2497                     return developer1.getId().compareTo( developer2.getId() );
2498                 }
2499 
2500                 if ( contributor1.getName() == null && contributor2.getName() != null )
2501                 {
2502                     return -1;
2503                 }
2504                 if ( contributor1.getName() != null && contributor2.getName() == null )
2505                 {
2506                     return +1;
2507                 }
2508                 return contributor1.getName().compareTo( contributor2.getName() );
2509             }
2510         } );
2511     }
2512 
2513     /**
2514      * Encapsulates all user messages.
2515      *
2516      * @since 1.1
2517      */
2518     private class UserMessages
2519     {
2520         public static final int REQUIRED = 10;
2521 
2522         public static final int REQUIRED_BY_ASF_OR_RECOMMENDED = 11;
2523 
2524         public static final int REQUIRED_BY_ASF = 12;
2525 
2526         public static final int RECOMMENDED = 20;
2527 
2528         public static final int INVALID_URL = 30;
2529 
2530         public static final int INVALID_DATE = 31;
2531 
2532         public static final int INVALID_ISO_DATE = 32;
2533 
2534         public static final int INVALID_EMAIL = 33;
2535 
2536         public static final int SHORT_DESC_TOO_LONG = 34;
2537 
2538         private List<String> errorMessages = new ArrayList<String>();
2539 
2540         private List<String> warnMessages = new ArrayList<String>();
2541 
2542         /**
2543          * @return the error messages
2544          */
2545         public List<String> getErrorMessages()
2546         {
2547             return errorMessages;
2548         }
2549 
2550         /**
2551          * @return the warn messages
2552          */
2553         public List<String> getWarnMessages()
2554         {
2555             return warnMessages;
2556         }
2557 
2558         /**
2559          * @param tags    not null
2560          * @param value   could be null
2561          * @param errorId positive id
2562          */
2563         protected void addMessage( String[] tags, String value, int errorId )
2564         {
2565             if ( tags == null )
2566             {
2567                 throw new IllegalArgumentException( "tags is required" );
2568             }
2569 
2570             boolean isPom = false;
2571             if ( tags[0].equalsIgnoreCase( "project" ) )
2572             {
2573                 isPom = true;
2574             }
2575             switch ( errorId )
2576             {
2577                 case REQUIRED:
2578                     errorMessages.add( "A " + toConfiguration( tags, null ) + "  parameter is required." );
2579                     break;
2580                 case REQUIRED_BY_ASF_OR_RECOMMENDED:
2581                     if ( isPom )
2582                     {
2583                         if ( asfExtOptions.isIncluded() )
2584                         {
2585                             errorMessages.add(
2586                                 "A POM " + toConfiguration( tags, null ) + " value is required by ASF." );
2587                         }
2588                         else
2589                         {
2590                             warnMessages.add( "No POM " + toConfiguration( tags, null )
2591                                                   + " value is defined, it is highly recommended to have one." );
2592                         }
2593                     }
2594                     else
2595                     {
2596                         if ( asfExtOptions.isIncluded() )
2597                         {
2598                             errorMessages.add(
2599                                 "A " + toConfiguration( tags, null ) + " parameter is required by ASF." );
2600                         }
2601                         else
2602                         {
2603                             warnMessages.add( "No " + toConfiguration( tags, null )
2604                                                   + " parameter defined, it is highly recommended to have one." );
2605                         }
2606                     }
2607                     break;
2608                 case REQUIRED_BY_ASF:
2609                     if ( isPom )
2610                     {
2611                         errorMessages.add( "A POM " + toConfiguration( tags, null ) + " value is required by ASF." );
2612                     }
2613                     else
2614                     {
2615                         errorMessages.add( "A " + toConfiguration( tags, null ) + " parameter is required by ASF." );
2616                     }
2617                     break;
2618                 case RECOMMENDED:
2619                     warnMessages.add( "No " + toConfiguration( tags, null )
2620                                           + " parameter defined, it is highly recommended to have one." );
2621                     break;
2622                 case INVALID_URL:
2623                     if ( isPom )
2624                     {
2625                         errorMessages.add( "The POM " + toConfiguration( tags, value ) + " value is not a valid URL." );
2626                     }
2627                     else
2628                     {
2629                         errorMessages.add( "The " + toConfiguration( tags, value ) + " parameter is not a valid URL." );
2630                     }
2631                     break;
2632                 case INVALID_DATE:
2633                     errorMessages.add(
2634                         "The " + toConfiguration( tags, value ) + " parameter should be in YYYY-MM-DD." );
2635                     break;
2636                 case INVALID_EMAIL:
2637                     errorMessages.add( "The POM " + toConfiguration( tags, value ) + " value is not a valid email." );
2638                     break;
2639                 case INVALID_ISO_DATE:
2640                     errorMessages.add( "The " + toConfiguration( tags, value )
2641                         + " parameter is not a valid ISO language." );
2642                     break;
2643                 case SHORT_DESC_TOO_LONG:
2644                     errorMessages.add( "The " + toConfiguration( tags, value )
2645                         + " first sentence is too long maximum words number is 10." );
2646                     break;
2647                 default:
2648                     throw new IllegalArgumentException( "Unknown errorId=" + errorId );
2649             }
2650         }
2651 
2652         /**
2653          * @param tags  not null
2654          * @param value of the last tag, could be null
2655          * @return the XML configuration defined in tags.
2656          */
2657         protected String toConfiguration( String[] tags, String value )
2658         {
2659             if ( tags == null )
2660             {
2661                 throw new IllegalArgumentException( "tags is required" );
2662             }
2663 
2664             StringBuilder sb = new StringBuilder();
2665             for ( int i = 0; i < tags.length; i++ )
2666             {
2667                 if ( i == tags.length - 1 && StringUtils.isEmpty( value ) )
2668                 {
2669                     sb.append( "<" ).append( tags[i] ).append( "/>" );
2670                 }
2671                 else
2672                 {
2673                     sb.append( "<" ).append( tags[i] ).append( ">" );
2674                 }
2675             }
2676             if ( StringUtils.isNotEmpty( value ) )
2677             {
2678                 sb.append( value );
2679             }
2680             for ( int i = tags.length - 1; i >= 0; i-- )
2681             {
2682                 if ( !( i == tags.length - 1 && StringUtils.isEmpty( value ) ) )
2683                 {
2684                     sb.append( "</" ).append( tags[i] ).append( ">" );
2685                 }
2686             }
2687 
2688             return sb.toString();
2689         }
2690     }
2691 }