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