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.Collections;
31  import java.util.Comparator;
32  import java.util.Date;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.TimeZone;
37  
38  import org.apache.maven.artifact.Artifact;
39  import org.apache.maven.artifact.factory.ArtifactFactory;
40  import org.apache.maven.artifact.repository.ArtifactRepository;
41  import org.apache.maven.artifact.repository.metadata.ArtifactRepositoryMetadata;
42  import org.apache.maven.artifact.repository.metadata.RepositoryMetadata;
43  import org.apache.maven.artifact.repository.metadata.RepositoryMetadataManager;
44  import org.apache.maven.artifact.repository.metadata.RepositoryMetadataResolutionException;
45  import org.apache.maven.model.Contributor;
46  import org.apache.maven.model.Developer;
47  import org.apache.maven.model.License;
48  import org.apache.maven.model.MailingList;
49  import org.apache.maven.model.Scm;
50  import org.apache.maven.plugin.AbstractMojo;
51  import org.apache.maven.plugin.MojoExecutionException;
52  import org.apache.maven.plugin.doap.options.ASFExtOptions;
53  import org.apache.maven.plugin.doap.options.DoapOptions;
54  import org.apache.maven.plugin.doap.options.Standard;
55  import org.apache.maven.project.MavenProject;
56  import org.apache.maven.scm.manager.NoSuchScmProviderException;
57  import org.apache.maven.scm.manager.ScmManager;
58  import org.apache.maven.scm.provider.cvslib.repository.CvsScmProviderRepository;
59  import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
60  import org.apache.maven.scm.repository.ScmRepository;
61  import org.apache.maven.scm.repository.ScmRepositoryException;
62  import org.codehaus.plexus.i18n.I18N;
63  import org.codehaus.plexus.util.FileUtils;
64  import org.codehaus.plexus.util.StringUtils;
65  import org.codehaus.plexus.util.WriterFactory;
66  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
67  import org.codehaus.plexus.util.xml.XMLWriter;
68  import org.codehaus.plexus.util.xml.XmlWriterUtil;
69  
70  /**
71   * Generate a <a href="http://usefulinc.com/ns/doap">Description of a Project (DOAP)</a>
72   * file from the main information found in a POM.
73   * <br/>
74   * <b>Note</b>: The generated file is tailored for use by projects at
75   * <a href="http://projects.apache.org/doap.html">Apache</a>.
76   *
77   * @author Jason van Zyl
78   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
79   * @version $Id: DoapMojo.html 815332 2012-05-01 21:25:03Z hboutemy $
80   * @since 1.0-beta-1
81   * @goal generate
82   */
83  public class DoapMojo
84      extends AbstractMojo
85  {
86      /** UTC Time Zone */
87      private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone( "UTC" );
88  
89      /** Date format for <lastUpdated/> tag in the repository metadata, i.e.: yyyyMMddHHmmss */
90      private static final DateFormat REPOSITORY_DATE_FORMAT;
91  
92      /** Date format for DOAP file, i.e. ISO-8601 YYYY-MM-DD */
93      private static final DateFormat DOAP_DATE_FORMAT;
94  
95      static
96      {
97          REPOSITORY_DATE_FORMAT = new SimpleDateFormat( "yyyyMMddHHmmss", Locale.ENGLISH );
98          REPOSITORY_DATE_FORMAT.setTimeZone( UTC_TIME_ZONE );
99  
100         DOAP_DATE_FORMAT = new SimpleDateFormat( "yyyy-MM-dd", Locale.ENGLISH );
101         DOAP_DATE_FORMAT.setTimeZone( UTC_TIME_ZONE );
102     }
103 
104     // ----------------------------------------------------------------------
105     // Mojo components
106     // ----------------------------------------------------------------------
107 
108     /**
109      * Maven SCM Manager.
110      *
111      * @parameter expression="${component.org.apache.maven.scm.manager.ScmManager}"
112      * @required
113      * @readonly
114      * @since 1.0
115      */
116     private ScmManager scmManager;
117 
118     /**
119      * Artifact factory.
120      *
121      * @component
122      * @since 1.0
123      */
124     private ArtifactFactory artifactFactory;
125 
126     /**
127      * Used to resolve artifacts.
128      *
129      * @component
130      * @since 1.0
131      */
132     private RepositoryMetadataManager repositoryMetadataManager;
133 
134     /**
135      * Internationalization component.
136      *
137      * @component
138      * @since 1.0
139      */
140     private I18N i18n;
141 
142     // ----------------------------------------------------------------------
143     // Mojo parameters
144     // ----------------------------------------------------------------------
145 
146     /**
147      * The POM from which information will be extracted to create a DOAP file.
148      *
149      * @parameter expression="${project}"
150      * @required
151      */
152     private MavenProject project;
153 
154     /**
155      * The name of the DOAP file that will be generated.
156      *
157      * @parameter expression="${doapFile}"
158      * default-value="${project.reporting.outputDirectory}/doap_${project.artifactId}.rdf"
159      * @required
160      */
161     private File doapFile;
162 
163     /**
164      * The local repository where the artifacts are located.
165      *
166      * @parameter expression="${localRepository}"
167      * @required
168      * @readonly
169      * @since 1.0
170      */
171     private ArtifactRepository localRepository;
172 
173     /**
174      * The remote repositories where the artifacts are located.
175      *
176      * @parameter expression="${project.remoteArtifactRepositories}"
177      * @required
178      * @readonly
179      * @since 1.0
180      */
181     private List remoteRepositories;
182 
183     // ----------------------------------------------------------------------
184     // Doap options
185     // ----------------------------------------------------------------------
186 
187     /**
188      * The category which should be displayed in the DOAP file.
189      *
190      * @parameter expression="${category}"
191      * @deprecated Since 1.0. Instead of, configure <code>category</code> property in <code>doapOptions</code>
192      * parameter.
193      */
194     private String category;
195 
196     /**
197      * The programming language which should be displayed in the DOAP file.
198      *
199      * @parameter expression="${language}"
200      * @deprecated Since 1.0. Instead of, configure <code>programmingLanguage</code> property in
201      * <code>doapOptions</code> parameter.
202      */
203     private String language;
204 
205     /**
206      * Specific DOAP parameters, i.e. options that POM doesn't have any notions.
207      * <br/>
208      * Example:
209      * <pre>
210      * &lt;doapOptions&gt;
211      * &nbsp;&nbsp;&lt;programmingLanguage&gt;java&lt;/programmingLanguage&gt;
212      * &lt;/doapOptions&gt;
213      * </pre>
214      * <br/>
215      * See <a href="./apidocs/org/apache/maven/plugin/doap/options/DoapOptions.html">Javadoc</a>
216      * <br/>
217      *
218      * @parameter expression="${doapOptions}"
219      * @since 1.0
220      * @see <a href="http://usefulinc.com/ns/doap#">http://usefulinc.com/ns/doap#</a>
221      */
222     private DoapOptions doapOptions;
223 
224     /**
225      * Specific ASF extensions parameters, i.e. options that POM doesn't have any notions but required by ASF DOAP
226      * requirements.
227      * <br/>
228      * Example:
229      * <pre>
230      * &lt;asfExtOptions&gt;
231      * &nbsp;&nbsp;&lt;included&gt;true&lt;/included&gt;
232      * &nbsp;&nbsp;&lt;charter&gt;The mission of the Apache XXX project is to create and maintain software
233      * &nbsp;&nbsp;libraries that provide ...&lt;/charter&gt;
234      * &nbsp;&nbsp;...
235      * &lt;/asfExtOptions&gt;
236      * </pre>
237      * <b>Note</b>: By default, <code>asfExtOptions/included</code> is set to <code>true</code> to include the ASF
238      * extensions.
239      * <br/>
240      * See <a href="./apidocs/org/apache/maven/plugin/doap/options/ASFExtOptions.html">Javadoc</a>
241      * <br/>
242      *
243      * @parameter expression="${asfExtOptions}"
244      * @since 1.0
245      * @see <a href="http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext">
246      * http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext</a>
247      * @see <a href="http://projects.apache.org/docs/pmc.html">http://projects.apache.org/docs/pmc.html</a>
248      * @see <a href="http://projects.apache.org/docs/standards.html">http://projects.apache.org/docs/standards.html</a>
249      */
250     private ASFExtOptions asfExtOptions;
251 
252     /**
253      * The value for the <code>xml:lang</code> attribute used by the <code>&lt;rdf:RDF/&gt;<code>,
254      * <code>&lt;description/&gt;</code> and <code>&lt;shortdesc/&gt;</code> elements.
255      * <br/>
256      * POM doesn't have any notions about language.
257      * <br/>
258      * See <a href="http://www.w3.org/TR/REC-xml/#sec-lang-tag">http://www.w3.org/TR/REC-xml/#sec-lang-tag</a>
259      * <br/>
260      *
261      * @parameter expression="${lang}" default-value="en"
262      * @required
263      * @since 1.0
264      */
265     private String lang;
266 
267     /**
268      * The <code>about</code> URI-reference which should be displayed in the DOAP file.
269      * Example:
270      * <pre>
271      * &lt;rdf:RDF&gt;
272      * &nbsp;&nbsp;&lt;Project rdf:about="http://maven.apache.org/"&gt;
273      * &nbsp;&nbsp;...
274      * &nbsp;&nbsp;&lt;/Project&gt;
275      * &lt;/rdf:RDF&gt;
276      * </pre>
277      * See <a href="http://www.w3.org/TR/1999/REC-rdf-syntax-19990222/#aboutAttr">
278      * http://www.w3.org/TR/1999/REC-rdf-syntax-19990222/#aboutAttr</a>
279      * <br/>
280      *
281      * @parameter expression="${about}" default-value="${project.url}"
282      * @required
283      * @since 1.0
284      */
285     private String about;
286 
287     // ----------------------------------------------------------------------
288     // Public methods
289     // ----------------------------------------------------------------------
290 
291     /** {@inheritDoc} */
292     public void execute()
293         throws MojoExecutionException
294     {
295         // ----------------------------------------------------------------------------
296         // setup pretty print xml writer
297         // ----------------------------------------------------------------------------
298 
299         Writer w;
300         try
301         {
302             if ( !doapFile.getParentFile().exists() )
303             {
304                 FileUtils.mkdir( doapFile.getParentFile().getAbsolutePath() );
305             }
306 
307             w = WriterFactory.newXmlWriter( doapFile );
308         }
309         catch ( IOException e )
310         {
311             throw new MojoExecutionException( "Error creating DOAP file.", e );
312         }
313 
314         if ( asfExtOptions.isIncluded() )
315         {
316             getLog().info( "Generating an ASF DOAP file..." );
317         }
318         else
319         {
320             getLog().info( "Generating a pure DOAP file..." );
321         }
322 
323         XMLWriter writer = new PrettyPrintXMLWriter( w, project.getModel().getModelEncoding(), null );
324 
325         // ----------------------------------------------------------------------------
326         // Convert POM to DOAP
327         // ----------------------------------------------------------------------------
328 
329         DoapUtil.writeHeader( writer );
330 
331         // Heading
332         writer.startElement( "rdf:RDF" );
333         writer.addAttribute( "xml:lang", lang );
334         writer.addAttribute( "xmlns", "http://usefulinc.com/ns/doap#" );
335         writer.addAttribute( "xmlns:rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#" );
336         writer.addAttribute( "xmlns:foaf", "http://xmlns.com/foaf/0.1/" );
337         if ( asfExtOptions.isIncluded() )
338         {
339             writer.addAttribute( "xmlns:asfext", ASFExtOptions.ASFEXT_NAMESPACE );
340         }
341 
342         // Project
343         writer.startElement( "Project" );
344         if ( StringUtils.isNotEmpty( about ) )
345         {
346             writer.addAttribute( "rdf:about", about );
347         }
348         else
349         {
350             getLog().warn( "rdf:about should be required" );
351         }
352 
353         // name
354         writeName( writer );
355 
356         // description
357         writeDescription( writer );
358 
359         // created
360         writeCreated( writer );
361 
362         // homepage and old-homepage
363         writeHomepage( writer );
364 
365         // licenses
366         writeLicenses( writer );
367 
368         // programming-language
369         writeProgrammingLanguage( writer );
370 
371         // category
372         writeCategory( writer );
373 
374         // os
375         writeOS( writer );
376 
377         // SCM
378         writeSourceRepositories( writer );
379 
380         // bug-database
381         writeBugDatabase( writer );
382 
383         // mailing list
384         writeMailingList( writer );
385 
386         // download-page and download-mirror
387         writeDownloadPage( writer );
388 
389         // screenshots
390         writeScreenshots( writer );
391 
392         // wiki
393         writeWiki( writer );
394 
395         // Releases
396         writeReleases( writer );
397 
398         // Developers
399         writeDevelopersOrContributors( writer, project.getDevelopers() );
400 
401         // Contributors
402         writeDevelopersOrContributors( writer, project.getContributors() );
403 
404         // ASFext
405         if ( asfExtOptions.isIncluded() )
406         {
407             writeASFext( writer );
408         }
409 
410         writer.endElement(); // Project
411         writer.endElement(); // rdf:RDF
412 
413         try
414         {
415             w.close();
416         }
417         catch ( IOException e )
418         {
419             throw new MojoExecutionException( "Error when closing the writer.", e );
420         }
421     }
422 
423     // ----------------------------------------------------------------------
424     // Private methods
425     // ----------------------------------------------------------------------
426 
427     /**
428      * Write DOAP name.
429      *
430      * @param writer not null
431      * @see <a href="http://usefulinc.com/ns/doap#name">http://usefulinc.com/ns/doap#name</a>
432      */
433     private void writeName( XMLWriter writer )
434     {
435         if ( StringUtils.isEmpty( project.getName() ) )
436         {
437             return;
438         }
439 
440         XmlWriterUtil.writeLineBreak( writer );
441         XmlWriterUtil.writeCommentText( writer, "A name of something.", 2 );
442 
443         if ( asfExtOptions.isIncluded()
444             && !project.getName().toLowerCase( Locale.ENGLISH ).trim().startsWith( "apache" ) )
445         {
446             DoapUtil.writeRdfResourceElement( writer, "name", "Apache " + project.getName() );
447         }
448         else
449         {
450             DoapUtil.writeRdfResourceElement( writer, "name", project.getName() );
451         }
452     }
453 
454     /**
455      * Write DOAP description.
456      *
457      * @param writer not null
458      * @see <a href="http://usefulinc.com/ns/doap#description">http://usefulinc.com/ns/doap#description</a>
459      * @see <a href="http://usefulinc.com/ns/doap#shortdesc">http://usefulinc.com/ns/doap#shortdesc</a>
460      */
461     private void writeDescription( XMLWriter writer )
462     {
463         if ( StringUtils.isEmpty( project.getDescription() ) )
464         {
465             return;
466         }
467 
468         XmlWriterUtil.writeLineBreak( writer );
469         XmlWriterUtil.writeCommentText( writer, "Plain text description of a project, of 2-4 sentences in length.", 2 );
470         DoapUtil.writeElement( writer, "description", project.getDescription(), lang );
471         if ( StringUtils.isNotEmpty( doapOptions.getShortdesc() ) )
472         {
473             DoapUtil.writeElement( writer, "shortdesc", doapOptions.getShortdesc(), lang );
474         }
475         else
476         {
477             DoapUtil.writeElement( writer, "shortdesc", project.getDescription(), lang );
478         }
479     }
480 
481     /**
482      * Write DOAP created.
483      *
484      * @param writer not null
485      * @see <a href="http://usefulinc.com/ns/doap#created">http://usefulinc.com/ns/doap#created</a>
486      */
487     private void writeCreated( XMLWriter writer )
488     {
489         if ( StringUtils.isEmpty( project.getInceptionYear() ) )
490         {
491             return;
492         }
493 
494         XmlWriterUtil.writeLineBreak( writer );
495         XmlWriterUtil.writeCommentText( writer, "Date when something was created, in YYYY-MM-DD form. e.g. 2004-04-05",
496                                         2 );
497         DoapUtil.writeElement( writer, "created", project.getInceptionYear() + "-01-01" );
498     }
499 
500     /**
501      * Write DOAP homepage and old-homepage.
502      *
503      * @param writer not null
504      * @see <a href="http://usefulinc.com/ns/doap#homepage">http://usefulinc.com/ns/doap#homepage</a>
505      * @see <a href="http://usefulinc.com/ns/doap#old-homepage">http://usefulinc.com/ns/doap#old-homepage</a>
506      */
507     private void writeHomepage( XMLWriter writer )
508     {
509         if ( StringUtils.isNotEmpty( project.getUrl() ) )
510         {
511             XmlWriterUtil.writeLineBreak( writer );
512             XmlWriterUtil.writeCommentText( writer,
513                                             "URL of a project's homepage, associated with exactly one project.", 2 );
514             DoapUtil.writeRdfResourceElement( writer, "homepage", project.getUrl() );
515         }
516 
517         if ( StringUtils.isNotEmpty( doapOptions.getOldHomepage() ) )
518         {
519             XmlWriterUtil.writeLineBreak( writer );
520             XmlWriterUtil.writeCommentText(
521                                             writer,
522                                             "URL of a project's past homepage, associated with exactly one project.",
523                                             2 );
524             DoapUtil.writeRdfResourceElement( writer, "old-homepage", doapOptions.getOldHomepage() );
525         }
526     }
527 
528     /**
529      * Write DOAP programming-language.
530      *
531      * @param writer not null
532      * @see <a href="http://usefulinc.com/ns/doap#programming-language">
533      * http://usefulinc.com/ns/doap#programming-language</a>
534      */
535     private void writeProgrammingLanguage( XMLWriter writer )
536     {
537         if ( StringUtils.isEmpty( doapOptions.getProgrammingLanguage() ) && StringUtils.isEmpty( language ) )
538         {
539             return;
540         }
541 
542         XmlWriterUtil.writeLineBreak( writer );
543         XmlWriterUtil.writeCommentText( writer, "Programming language.", 2 );
544 
545         if ( StringUtils.isNotEmpty( language ) ) // backward compatible
546         {
547             if ( asfExtOptions.isIncluded() && !ASFExtOptions.isProgrammingLanguageSupportedByASF( language ) )
548             {
549                 getLog().warn(
550                                "The programming language '" + language + "' is not supported by ASF. "
551                                    + "Refer you to http://projects.apache.org/languages.html" );
552             }
553 
554             DoapUtil.writeRdfResourceElement( writer, "programming-language", language );
555         }
556 
557         if ( StringUtils.isNotEmpty( doapOptions.getProgrammingLanguage() ) )
558         {
559             String[] languages = StringUtils.split( doapOptions.getProgrammingLanguage(), "," );
560             for ( int i = 0; i < languages.length; i++ )
561             {
562                 if ( asfExtOptions.isIncluded()
563                     && !ASFExtOptions.isProgrammingLanguageSupportedByASF( languages[i].trim() ) )
564                 {
565                     getLog().warn(
566                                    "The programming language '" + languages[i].trim() + "' is not supported by ASF. "
567                                        + "Refer you to http://projects.apache.org/languages.html" );
568                 }
569 
570                 DoapUtil.writeRdfResourceElement( writer, "programming-language", languages[i].trim() );
571             }
572         }
573     }
574 
575     /**
576      * Write DOAP category.
577      *
578      * @param writer not null
579      * @see <a href="http://usefulinc.com/ns/doap#category">http://usefulinc.com/ns/doap#category</a>
580      */
581     private void writeCategory( XMLWriter writer )
582     {
583         if ( StringUtils.isEmpty( doapOptions.getCategory() ) && StringUtils.isEmpty( category ) )
584         {
585             return;
586         }
587 
588         //TODO: how to lookup category, map it, or just declare it.
589         XmlWriterUtil.writeLineBreak( writer );
590         XmlWriterUtil.writeCommentText( writer, "A category of project.", 2 );
591 
592         if ( StringUtils.isNotEmpty( category ) ) // backward compatible
593         {
594             if ( asfExtOptions.isIncluded() && !ASFExtOptions.isCategorySupportedByASF( category ) )
595             {
596                 getLog().warn(
597                                "The given category '" + category + "' is not supported by ASF. "
598                                    + "Refer you to http://projects.apache.org/categories.html" );
599             }
600 
601             if ( asfExtOptions.isIncluded() )
602             {
603                 DoapUtil
604                     .writeRdfResourceElement( writer, "category", "http://projects.apache.org/category/" + category );
605             }
606             else
607             {
608                 DoapUtil.writeRdfResourceElement( writer, "category", category );
609             }
610         }
611 
612         if ( StringUtils.isNotEmpty( doapOptions.getCategory() ) )
613         {
614             String[] categories = StringUtils.split( doapOptions.getCategory(), "," );
615             for ( int i = 0; i < categories.length; i++ )
616             {
617                 if ( asfExtOptions.isIncluded() && !ASFExtOptions.isCategorySupportedByASF( categories[i] ) )
618                 {
619                     getLog().warn(
620                                    "The given category '" + categories[i] + "' is not supported by ASF. "
621                                        + "Refer you to http://projects.apache.org/categories.html" );
622                 }
623 
624                 if ( asfExtOptions.isIncluded() )
625                 {
626                     DoapUtil.writeRdfResourceElement( writer, "category", "http://projects.apache.org/category/"
627                         + categories[i].trim() );
628                 }
629                 else
630                 {
631                     DoapUtil.writeRdfResourceElement( writer, "category", categories[i].trim() );
632                 }
633             }
634         }
635     }
636 
637     /**
638      * Write DOAP download-page and download-mirror.
639      *
640      * @param writer not null
641      * @see <a href="http://usefulinc.com/ns/doap#download-page">http://usefulinc.com/ns/doap#download-page</a>
642      * @see <a href="http://usefulinc.com/ns/doap#download-mirror">http://usefulinc.com/ns/doap#download-mirror</a>
643      */
644     private void writeDownloadPage( XMLWriter writer )
645     {
646         if ( StringUtils.isEmpty( doapOptions.getDownloadPage() ) )
647         {
648             if ( StringUtils.isNotEmpty( project.getUrl() ) )
649             {
650                 doapOptions.setDownloadPage( composeUrl( project.getUrl(), "/download.html" ) );
651             }
652         }
653 
654         if ( StringUtils.isNotEmpty( doapOptions.getDownloadPage() ) )
655         {
656             XmlWriterUtil.writeLineBreak( writer );
657             XmlWriterUtil.writeCommentText( writer, "Download page.", 2 );
658             DoapUtil.writeRdfResourceElement( writer, "download-page", doapOptions.getDownloadPage() );
659         }
660 
661         if ( StringUtils.isNotEmpty( doapOptions.getDownloadMirror() ) )
662         {
663             XmlWriterUtil.writeLineBreak( writer );
664             XmlWriterUtil.writeCommentText( writer, "Mirror of software download web page.", 2 );
665             String[] downloadMirrors = StringUtils.split( doapOptions.getDownloadMirror(), "," );
666             for ( int i = 0; i < downloadMirrors.length; i++ )
667             {
668                 DoapUtil.writeRdfResourceElement( writer, "download-mirror", downloadMirrors[i].trim() );
669             }
670         }
671     }
672 
673     /**
674      * Write DOAP OS.
675      *
676      * @param writer not null
677      * @see <a href="http://usefulinc.com/ns/doap#os">http://usefulinc.com/ns/doap#os</a>
678      */
679     private void writeOS( XMLWriter writer )
680     {
681         if ( StringUtils.isEmpty( doapOptions.getOs() ) )
682         {
683             return;
684         }
685 
686         XmlWriterUtil.writeLineBreak( writer );
687         XmlWriterUtil.writeCommentText( writer, "Operating system that a project is limited to.", 2 );
688 
689         String[] oses = StringUtils.split( doapOptions.getOs(), "," );
690         for ( int i = 0; i < oses.length; i++ )
691         {
692             DoapUtil.writeRdfResourceElement( writer, "os", oses[i].trim() );
693         }
694     }
695 
696     /**
697      * Write DOAP screenshots.
698      *
699      * @param writer not null
700      * @see <a href="http://usefulinc.com/ns/doap#screenshots">http://usefulinc.com/ns/doap#screenshots</a>
701      */
702     private void writeScreenshots( XMLWriter writer )
703     {
704         if ( StringUtils.isEmpty( doapOptions.getScreenshots() ) )
705         {
706             return;
707         }
708 
709         XmlWriterUtil.writeLineBreak( writer );
710         XmlWriterUtil.writeCommentText( writer, "Web page with screenshots of project.", 2 );
711         DoapUtil.writeRdfResourceElement( writer, "screenshots", doapOptions.getScreenshots() );
712     }
713 
714     /**
715      * Write DOAP wiki.
716      *
717      * @param writer not null
718      * @see <a href="http://usefulinc.com/ns/doap#wiki">http://usefulinc.com/ns/doap#wiki</a>
719      */
720     private void writeWiki( XMLWriter writer )
721     {
722         if ( StringUtils.isEmpty( doapOptions.getWiki() ) )
723         {
724             return;
725         }
726 
727         XmlWriterUtil.writeLineBreak( writer );
728         XmlWriterUtil.writeCommentText( writer, "URL of Wiki for collaborative discussion of project.", 2 );
729         DoapUtil.writeRdfResourceElement( writer, "wiki", doapOptions.getWiki() );
730     }
731 
732     /**
733      * Write DOAP licenses.
734      *
735      * @param writer not null
736      * @see <a href="http://usefulinc.com/ns/doap#license">http://usefulinc.com/ns/doap#license</a>
737      */
738     private void writeLicenses( XMLWriter writer )
739     {
740         if ( project.getLicenses() == null || project.getLicenses().isEmpty() )
741         {
742             return;
743         }
744 
745         XmlWriterUtil.writeLineBreak( writer );
746         XmlWriterUtil.writeCommentText( writer, "The URI of the license the software is distributed under.", 2 );
747         //TODO: how to map to usefulinc site, or if this is necessary, the OSI page might
748         //      be more appropriate.
749         for ( Iterator it = project.getLicenses().iterator(); it.hasNext(); )
750         {
751             License license = (License) it.next();
752 
753             if ( StringUtils.isNotEmpty( license.getUrl() ) )
754             {
755                 DoapUtil.writeRdfResourceElement( writer, "license", license.getUrl() );
756             }
757             else
758             {
759                 getLog().warn( "No URL was specified for license " + license.getName() );
760             }
761         }
762     }
763 
764     /**
765      * Write DOAP bug-database.
766      *
767      * @param writer not null
768      * @see <a href="http://usefulinc.com/ns/doap#bug-database">http://usefulinc.com/ns/doap#bug-database</a>
769      */
770     private void writeBugDatabase( XMLWriter writer )
771     {
772         if ( project.getIssueManagement() == null )
773         {
774             return;
775         }
776 
777         XmlWriterUtil.writeLineBreak( writer );
778         XmlWriterUtil.writeCommentText( writer, "bug database.", 2 );
779         if ( StringUtils.isNotEmpty( project.getIssueManagement().getUrl() ) )
780         {
781             DoapUtil.writeRdfResourceElement( writer, "bug-database", project.getIssueManagement().getUrl() );
782         }
783         else
784         {
785             getLog().warn( "No URL was specified for issue management" );
786         }
787     }
788 
789     /**
790      * Write DOAP mailing-list.
791      *
792      * @param writer not null
793      * @see <a href="http://usefulinc.com/ns/doap#mailing-list">http://usefulinc.com/ns/doap#mailing-list</a>
794      */
795     private void writeMailingList( XMLWriter writer )
796     {
797         if ( project.getMailingLists() == null || project.getMailingLists().isEmpty() )
798         {
799             return;
800         }
801 
802         XmlWriterUtil.writeLineBreak( writer );
803         XmlWriterUtil.writeCommentText( writer, "mailing list.", 2 );
804         for ( Iterator it = project.getMailingLists().iterator(); it.hasNext(); )
805         {
806             MailingList mailingList = (MailingList) it.next();
807 
808             if ( StringUtils.isNotEmpty( mailingList.getArchive() ) )
809             {
810                 DoapUtil.writeRdfResourceElement( writer, "mailing-list", mailingList.getArchive() );
811             }
812             else
813             {
814                 getLog().warn( "No archive was specified for mailing list " + mailingList.getName() );
815             }
816 
817             if ( mailingList.getOtherArchives() != null )
818             {
819                 for ( Iterator it2 = mailingList.getOtherArchives().iterator(); it2.hasNext(); )
820                 {
821                     String otherArchive = (String) it2.next();
822 
823                     if ( StringUtils.isNotEmpty( otherArchive ) )
824                     {
825                         DoapUtil.writeRdfResourceElement( writer, "mailing-list", otherArchive );
826                     }
827                     else
828                     {
829                         getLog().warn( "No other archive was specified for mailing list " + mailingList.getName() );
830                     }
831                 }
832             }
833         }
834     }
835 
836     /**
837      * Write all DOAP releases.
838      *
839      * @param writer not null
840      * @throws MojoExecutionException if any
841      * @see <a href="http://usefulinc.com/ns/doap#release">http://usefulinc.com/ns/doap#release</a>
842      * @see <a href="http://usefulinc.com/ns/doap#Version">http://usefulinc.com/ns/doap#Version</a>
843      */
844     private void writeReleases( XMLWriter writer )
845         throws MojoExecutionException
846     {
847         Artifact artifact = artifactFactory.createArtifact( project.getGroupId(), project.getArtifactId(), project
848             .getVersion(), null, project.getPackaging() );
849         RepositoryMetadata metadata = new ArtifactRepositoryMetadata( artifact );
850 
851         for ( Iterator it = remoteRepositories.iterator(); it.hasNext(); )
852         {
853             ArtifactRepository repo = (ArtifactRepository) it.next();
854 
855             if ( repo.isBlacklisted() )
856             {
857                 continue;
858             }
859             if ( repo.getSnapshots().isEnabled() )
860             {
861                 continue;
862             }
863             if ( repo.getReleases().isEnabled() )
864             {
865                 try
866                 {
867                     repositoryMetadataManager.resolveAlways( metadata, localRepository, repo );
868                     break;
869                 }
870                 catch ( RepositoryMetadataResolutionException e )
871                 {
872                     throw new MojoExecutionException( metadata
873                         + " could not be retrieved from repositories due to an error: " + e.getMessage(), e );
874                 }
875             }
876         }
877 
878         if ( metadata.getMetadata().getVersioning() == null )
879         {
880             getLog().info( "No versioning was found - ignored writing <release/> tag." );
881             return;
882         }
883 
884         List versions = metadata.getMetadata().getVersioning().getVersions();
885 
886         // Recent releases in first
887         Collections.reverse( versions );
888         boolean addComment = false;
889         int i = 0;
890         for ( Iterator it = versions.iterator(); it.hasNext(); )
891         {
892             String version = (String) it.next();
893 
894             if ( !addComment )
895             {
896                 XmlWriterUtil.writeLineBreak( writer );
897                 XmlWriterUtil.writeCommentText( writer, "Project releases.", 2 );
898                 addComment = true;
899             }
900 
901             writer.startElement( "release" );
902             writer.startElement( "Version" );
903 
904             writer.startElement( "name" );
905             if ( version.equals( metadata.getMetadata().getVersioning().getRelease() ) )
906             {
907                 writer.writeText( "Latest stable release" );
908             }
909             else
910             {
911                 writer.writeText( project.getName() + " - " + version );
912             }
913             writer.endElement(); // name
914 
915             writer.startElement( "revision" );
916             writer.writeText( version );
917             writer.endElement(); // revision
918 
919             // list all file release from all remote repos
920             for ( Iterator it2 = remoteRepositories.iterator(); it2.hasNext(); )
921             {
922                 ArtifactRepository repo = (ArtifactRepository) it2.next();
923 
924                 Artifact artifactRelease = artifactFactory.createArtifact( project.getGroupId(), project
925                     .getArtifactId(), version, null, project.getPackaging() );
926 
927                 if ( artifactRelease == null )
928                 {
929                     continue;
930                 }
931 
932                 String fileRelease = repo.getUrl() + "/" + repo.pathOf( artifactRelease );
933                 // try to ping the url
934                 try
935                 {
936                     URL urlRelease = new URL( fileRelease );
937                     urlRelease.openStream();
938                 }
939                 catch ( MalformedURLException e )
940                 {
941                     getLog().debug( e.getMessage(), e );
942                     continue;
943                 }
944                 catch ( IOException e )
945                 {
946                     // Not found, ignored
947                     getLog().debug( e.getMessage(), e );
948                     continue;
949                 }
950 
951                 writer.startElement( "file-release" );
952                 writer.writeText( fileRelease );
953                 writer.endElement(); // file-release
954 
955                 Date releaseDate = null;
956                 try
957                 {
958                     releaseDate =
959                         REPOSITORY_DATE_FORMAT.parse( metadata.getMetadata().getVersioning().getLastUpdated() );
960                 }
961                 catch ( ParseException e )
962                 {
963                     getLog().error(
964                                     "Unable to parse date '"
965                                         + metadata.getMetadata().getVersioning().getLastUpdated() + "'" );
966                     continue;
967                 }
968 
969                 // See MDOAP-11
970                 if ( i == 0 )
971                 {
972                     writer.startElement( "created" );
973                     writer.writeText( DOAP_DATE_FORMAT.format( releaseDate ) );
974                     writer.endElement(); // created
975                 }
976             }
977 
978             writer.endElement(); // Version
979             writer.endElement(); // release
980 
981             i++;
982         }
983     }
984 
985     /**
986      * Write all DOAP repositories.
987      *
988      * @param writer not null
989      * @see <a href="http://usefulinc.com/ns/doap#Repository">http://usefulinc.com/ns/doap#Repository</a>
990      * @see <a href="http://usefulinc.com/ns/doap#CVSRepository">http://usefulinc.com/ns/doap#CVSRepository</a>
991      * @see <a href="http://usefulinc.com/ns/doap#SVNRepository">http://usefulinc.com/ns/doap#SVNRepository</a>
992      */
993     private void writeSourceRepositories( XMLWriter writer )
994     {
995         Scm scm = project.getScm();
996         if ( scm == null )
997         {
998             return;
999         }
1000 
1001         XmlWriterUtil.writeLineBreak( writer );
1002         XmlWriterUtil.writeCommentText( writer, "Anonymous Source Repository", 2 );
1003         String anonymousConnection = scm.getConnection();
1004         writeSourceRepository( writer, anonymousConnection );
1005 
1006         XmlWriterUtil.writeLineBreak( writer );
1007         XmlWriterUtil.writeCommentText( writer, "Developer Source Repository", 2 );
1008         String developerConnection = scm.getDeveloperConnection();
1009         writeSourceRepository( writer, developerConnection );
1010     }
1011 
1012     /**
1013      * Write a DOAP repository, for instance:
1014      * <pre>
1015      *   &lt;repository&gt;
1016      *     &lt;SVNRepository&gt;
1017      *       &lt;location rdf:resource="http://svn.apache.org/repos/asf/maven/components/trunk/"/&gt;
1018      *       &lt;browse rdf:resource="http://svn.apache.org/viewcvs.cgi/maven/components/trunk/"/&gt;
1019      *     &lt;/SVNRepository&gt;
1020      *   &lt;/repository&gt;
1021      * </pre>
1022      *
1023      * @param writer not null
1024      * @param connection not null
1025      * @see <a href="http://usefulinc.com/ns/doap#Repository">http://usefulinc.com/ns/doap#Repository</a>
1026      * @see <a href="http://usefulinc.com/ns/doap#CVSRepository">http://usefulinc.com/ns/doap#CVSRepository</a>
1027      * @see <a href="http://usefulinc.com/ns/doap#SVNRepository">http://usefulinc.com/ns/doap#SVNRepository</a>
1028      */
1029     private void writeSourceRepository( XMLWriter writer, String connection )
1030     {
1031         ScmRepository repository = getScmRepository( connection );
1032 
1033         writer.startElement( "repository" );
1034 
1035         if ( isScmSystem( repository, "cvs" ) )
1036         {
1037             writer.startElement( "CVSRepository" );
1038 
1039             CvsScmProviderRepository cvsRepo = (CvsScmProviderRepository) repository.getProviderRepository();
1040 
1041             DoapUtil.writeElement( writer, "anon-root", cvsRepo.getCvsRoot() );
1042             DoapUtil.writeElement( writer, "module", cvsRepo.getModule() );
1043         }
1044         else if ( isScmSystem( repository, "svn" ) )
1045         {
1046             writer.startElement( "SVNRepository" );
1047 
1048             SvnScmProviderRepository svnRepo = (SvnScmProviderRepository) repository.getProviderRepository();
1049 
1050             DoapUtil.writeRdfResourceElement( writer, "location", svnRepo.getUrl() );
1051         }
1052         else
1053         {
1054             /*
1055              * Supported DOAP repositories actually unsupported by SCM:
1056              *   BitKeeper (http://usefulinc.com/ns/doap#BKRepository)
1057              *   Arch (http://usefulinc.com/ns/doap#ArchRepository)
1058              * Other SCM repos are unsupported by DOAP.
1059              */
1060             writer.startElement( "Repository" );
1061 
1062             if ( connection.length() < 4 )
1063             {
1064                 throw new IllegalArgumentException( "The source repository connection is too short." );
1065             }
1066 
1067             DoapUtil.writeRdfResourceElement( writer, "location", connection.substring( 4 ) );
1068         }
1069 
1070         DoapUtil.writeRdfResourceElement( writer, "browse", project.getScm().getUrl() );
1071 
1072         writer.endElement(); // CVSRepository || SVNRepository || Repository
1073         writer.endElement(); // repository
1074     }
1075 
1076     /**
1077      * Write all DOAP persons.
1078      *
1079      * @param writer not null
1080      * @param developersOrContributors list of developers or contributors
1081      */
1082     private void writeDevelopersOrContributors( XMLWriter writer, List developersOrContributors )
1083     {
1084         if ( developersOrContributors == null || developersOrContributors.isEmpty() )
1085         {
1086             return;
1087         }
1088 
1089         boolean isDeveloper = Developer.class.isAssignableFrom( developersOrContributors.get( 0 ).getClass() );
1090         if ( isDeveloper )
1091         {
1092             XmlWriterUtil.writeLineBreak( writer );
1093             XmlWriterUtil.writeCommentText( writer, "Main committers", 2 );
1094         }
1095         else
1096         {
1097             XmlWriterUtil.writeLineBreak( writer );
1098             XmlWriterUtil.writeCommentText( writer, "Contributed persons", 2 );
1099         }
1100 
1101         List maintainers =  DoapUtil.getDevelopersOrContributorsWithMaintainerRole( i18n, developersOrContributors );
1102         List developers = DoapUtil.getDevelopersOrContributorsWithDeveloperRole( i18n, developersOrContributors );
1103         List documenters = DoapUtil.getDevelopersOrContributorsWithDocumenterRole( i18n, developersOrContributors );
1104         List translators = DoapUtil.getDevelopersOrContributorsWithTranslatorRole( i18n, developersOrContributors );
1105         List testers = DoapUtil.getDevelopersOrContributorsWithTesterRole( i18n, developersOrContributors );
1106         List helpers = DoapUtil.getDevelopersOrContributorsWithHelperRole( i18n, developersOrContributors );
1107         List unknowns = DoapUtil.getDevelopersOrContributorsWithUnknownRole( i18n, developersOrContributors );
1108 
1109         // By default, all developers are maintainers and contributors are helpers
1110         if ( isDeveloper )
1111         {
1112             maintainers.addAll( unknowns );
1113         }
1114         else
1115         {
1116             helpers.addAll( unknowns );
1117         }
1118 
1119         // all alphabetical
1120         if ( developers.size() != 0 )
1121         {
1122             writeDeveloperOrContributor( writer, developers, "developer" );
1123         }
1124         if ( documenters.size() != 0 )
1125         {
1126             writeDeveloperOrContributor( writer, documenters, "documenter" );
1127         }
1128         if ( helpers.size() != 0 )
1129         {
1130             writeDeveloperOrContributor( writer, helpers, "helper" );
1131         }
1132         if ( maintainers.size() != 0 )
1133         {
1134             writeDeveloperOrContributor( writer, maintainers, "maintainer" );
1135         }
1136         if ( testers.size() != 0 )
1137         {
1138             writeDeveloperOrContributor( writer, testers, "tester" );
1139         }
1140         if ( translators.size() != 0 )
1141         {
1142             writeDeveloperOrContributor( writer, translators, "translator" );
1143         }
1144     }
1145 
1146     /**
1147      * Write a DOAP maintainer or developer or documenter or translator or tester or helper, for instance:
1148      * <pre>
1149      *   &lt;maintainer&gt;
1150      *     &lt;foaf:Person&gt;
1151      *       &lt;foaf:name&gt;Emmanuel Venisse&lt;/foaf:name&gt;
1152      *       &lt;foaf:mbox rdf:resource="mailto:evenisse@apache.org"/&gt;
1153      *     &lt;/foaf:Person&gt;
1154      *   &lt;/maintainer&gt;
1155      * </pre>
1156      *
1157      * @param writer not null
1158      * @param developersOrContributors list of <code>{@link Developer}/{@link Contributor}</code>
1159      * @param doapType not null
1160      * @see #writeDeveloperOrContributor(XMLWriter, Object, String)
1161      */
1162     private void writeDeveloperOrContributor( XMLWriter writer, List developersOrContributors, String doapType )
1163     {
1164         if ( developersOrContributors == null || developersOrContributors.isEmpty() )
1165         {
1166             return;
1167         }
1168 
1169         // Sort list by names
1170         Collections.sort( developersOrContributors, new Comparator()
1171         {
1172             /**
1173              * {@inheritDoc}
1174              */
1175             public int compare( Object arg0, Object arg1 )
1176             {
1177                 if ( Developer.class.isAssignableFrom( arg0.getClass() ) )
1178                 {
1179                     Developer developer0 = (Developer) arg0;
1180                     Developer developer1 = (Developer) arg1;
1181 
1182                     if ( developer0.getName() == null )
1183                     {
1184                         return -1;
1185                     }
1186                     if ( developer1.getName() == null )
1187                     {
1188                         return +1;
1189                     }
1190 
1191                     return developer0.getName().compareTo( developer1.getName() );
1192                 }
1193 
1194                 Contributor contributor0 = (Contributor) arg0;
1195                 Contributor contributor1 = (Contributor) arg1;
1196 
1197                 if ( contributor0.getName() == null )
1198                 {
1199                     return -1;
1200                 }
1201                 if ( contributor1.getName() == null )
1202                 {
1203                     return +1;
1204                 }
1205 
1206                 return contributor0.getName().compareTo( contributor1.getName() );
1207             }
1208         } );
1209 
1210         for ( Iterator it = developersOrContributors.iterator(); it.hasNext(); )
1211         {
1212             Object obj = it.next();
1213             writeDeveloperOrContributor( writer, obj, doapType );
1214         }
1215     }
1216 
1217     /**
1218      * Writer a single developer or contributor
1219      *
1220      * @param writer not null
1221      * @param developerOrContributor not null, instance of <code>{@link Developer}/{@link Contributor}</code>
1222      * @param doapType not null
1223      * @see <a href="http://usefulinc.com/ns/doap#maintainer">http://usefulinc.com/ns/doap#maintainer</a>
1224      * @see <a href="http://usefulinc.com/ns/doap#developer">http://usefulinc.com/ns/doap#developer</a>
1225      * @see <a href="http://usefulinc.com/ns/doap#documenter">http://usefulinc.com/ns/doap#documenter</a>
1226      * @see <a href="http://usefulinc.com/ns/doap#translator">http://usefulinc.com/ns/doap#translator</a>
1227      * @see <a href="http://usefulinc.com/ns/doap#tester">http://usefulinc.com/ns/doap#tester</a>
1228      * @see <a href="http://usefulinc.com/ns/doap#helper">http://usefulinc.com/ns/doap#helper</a>
1229      * @see <a href="http://xmlns.com/foaf/0.1/Person">http://xmlns.com/foaf/0.1/Person</a>
1230      * @see <a href="http://xmlns.com/foaf/0.1/name">http://xmlns.com/foaf/0.1/name</a>
1231      * @see <a href="http://xmlns.com/foaf/0.1/mbox">http://xmlns.com/foaf/0.1/mbox</a>
1232      * @see <a href="http://xmlns.com/foaf/0.1/Organization">http://xmlns.com/foaf/0.1/Organization</a>
1233      * @see <a href="http://xmlns.com/foaf/0.1/homepage">http://xmlns.com/foaf/0.1/homepage</a>
1234      */
1235     private void writeDeveloperOrContributor( XMLWriter writer, Object developerOrContributor, String doapType )
1236     {
1237         if ( developerOrContributor == null )
1238         {
1239             return;
1240         }
1241 
1242         if ( StringUtils.isEmpty( doapType ) )
1243         {
1244             throw new IllegalArgumentException( "doapType is required." );
1245         }
1246 
1247         String name;
1248         String email;
1249         String organization;
1250         String homepage;
1251 
1252         if ( Developer.class.isAssignableFrom( developerOrContributor.getClass() ) )
1253         {
1254             Developer d = (Developer) developerOrContributor;
1255             name = d.getName();
1256             email = d.getEmail();
1257             organization = d.getOrganization();
1258             homepage = d.getUrl();
1259         }
1260         else
1261         {
1262             Contributor c = (Contributor) developerOrContributor;
1263             name = c.getName();
1264             email = c.getEmail();
1265             organization = c.getOrganization();
1266             homepage = c.getUrl();
1267         }
1268 
1269         // Name is required to write doap
1270         if ( StringUtils.isEmpty( name ) )
1271         {
1272             return;
1273         }
1274 
1275         writer.startElement( doapType );
1276         writer.startElement( "foaf:Person" );
1277         writer.startElement( "foaf:name" );
1278         writer.writeText( name );
1279         writer.endElement(); // foaf:name
1280         if ( StringUtils.isNotEmpty( email ) )
1281         {
1282             DoapUtil.writeRdfResourceElement( writer, "foaf:mbox", "mailto:" + email );
1283         }
1284         if ( StringUtils.isNotEmpty( organization ) )
1285         {
1286             DoapUtil.writeRdfResourceElement( writer, "foaf:Organization", organization );
1287         }
1288         if ( StringUtils.isNotEmpty( homepage ) )
1289         {
1290             DoapUtil.writeRdfResourceElement( writer, "foaf:homepage", homepage );
1291         }
1292         writer.endElement(); // foaf:Person
1293         writer.endElement(); // doapType
1294     }
1295 
1296     /**
1297      * Return a <code>SCM repository</code> defined by a given url
1298      *
1299      * @param scmUrl an SCM URL
1300      * @return a valid SCM repository or null
1301      */
1302     private ScmRepository getScmRepository( String scmUrl )
1303     {
1304         ScmRepository repo = null;
1305         if ( !StringUtils.isEmpty( scmUrl ) )
1306         {
1307             try
1308             {
1309                 repo = scmManager.makeScmRepository( scmUrl );
1310             }
1311             catch ( NoSuchScmProviderException e )
1312             {
1313                 if ( getLog().isDebugEnabled() )
1314                 {
1315                     getLog().debug( e.getMessage(), e );
1316                 }
1317             }
1318             catch ( ScmRepositoryException e )
1319             {
1320                 if ( getLog().isDebugEnabled() )
1321                 {
1322                     getLog().debug( e.getMessage(), e );
1323                 }
1324             }
1325         }
1326 
1327         return repo;
1328     }
1329 
1330     /**
1331      * Write the ASF extensions
1332      *
1333      * @param writer not null
1334      * @see <a href="http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext">
1335      * http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext</a>
1336      * @see <a href="http://projects.apache.org/docs/pmc.html">http://projects.apache.org/docs/pmc.html</a>
1337      */
1338     private void writeASFext( XMLWriter writer )
1339     {
1340         XmlWriterUtil.writeLineBreak( writer );
1341         XmlWriterUtil.writeCommentText( writer, "ASF extension", 2 );
1342 
1343         // asfext:pmc
1344         if ( StringUtils.isNotEmpty( asfExtOptions.getPmc() ) )
1345         {
1346             DoapUtil.writeRdfResourceElement( writer, "asfext:pmc", asfExtOptions.getPmc() );
1347         }
1348         else
1349         {
1350             if ( StringUtils.isEmpty( project.getUrl() ) )
1351             {
1352                 getLog().warn(
1353                                "No project url discovered! According http://projects.apache.org/docs/pmc.html, "
1354                                    + "asfext:pmc is required" );
1355             }
1356             else
1357             {
1358                 DoapUtil.writeRdfResourceElement( writer, "asfext:pmc", project.getUrl() );
1359             }
1360         }
1361 
1362         // asfext:name
1363         if ( StringUtils.isNotEmpty( asfExtOptions.getName() ) )
1364         {
1365             DoapUtil.writeRdfResourceElement( writer, "asfext:name", asfExtOptions.getName() );
1366         }
1367         else
1368         {
1369             if ( StringUtils.isEmpty( project.getName() ) )
1370             {
1371                 getLog().warn(
1372                                "No project name discovered! According http://projects.apache.org/docs/pmc.html, "
1373                                    + "asfext:name is required" );
1374             }
1375             else
1376             {
1377                 // Respect ASF rule
1378                 if ( !project.getName().trim().startsWith( "Apache" ) )
1379                 {
1380                     DoapUtil.writeRdfResourceElement( writer, "asfext:name", "Apache " + project.getName().trim() );
1381                 }
1382                 else
1383                 {
1384                     DoapUtil.writeRdfResourceElement( writer, "asfext:name", project.getName().trim() );
1385                 }
1386             }
1387         }
1388 
1389         // asfext:charter
1390         if ( StringUtils.isEmpty( asfExtOptions.getCharter() ) )
1391         {
1392             getLog().warn(
1393                            "No charter specified! According http://projects.apache.org/docs/pmc.html, "
1394                                + "charter is required" );
1395         }
1396         else
1397         {
1398             DoapUtil.writeRdfResourceElement( writer, "asfext:charter", asfExtOptions.getCharter() );
1399         }
1400 
1401         // asfext:chair
1402         List developers = project.getDevelopers();
1403 
1404         if ( StringUtils.isNotEmpty( asfExtOptions.getChair() ) )
1405         {
1406             writer.startElement( "asfext:chair" );
1407             writer.startElement( "foaf:Person" );
1408             writer.startElement( "foaf:name" );
1409             writer.writeText( asfExtOptions.getChair() );
1410             writer.endElement(); // foaf:name
1411             writer.endElement(); // foaf:Person
1412             writer.endElement(); // asfext:chair
1413         }
1414         else
1415         {
1416             Developer chair = ASFExtOptions.findChair( developers );
1417             if ( chair != null )
1418             {
1419                 writeDeveloperOrContributor( writer, chair, "asfext:chair" );
1420             }
1421             else
1422             {
1423                 getLog().warn(
1424                                "No chair man discovered! According http://projects.apache.org/docs/pmc.html, "
1425                                    + "asfext:chair is required" );
1426             }
1427         }
1428 
1429         // asfext:member
1430         if ( developers != null && developers.size() > 0 )
1431         {
1432             List pmcMember = ASFExtOptions.findPMCMembers( developers );
1433             for ( Iterator it = pmcMember.iterator(); it.hasNext(); )
1434             {
1435                 Developer developer = (Developer) it.next();
1436 
1437                 writeDeveloperOrContributor( writer, developer, "asfext:member" );
1438             }
1439         }
1440 
1441         writeASFImplements( writer );
1442     }
1443 
1444     /**
1445      * Write the ASF implements.
1446      *
1447      * @param writer not null
1448      * @see <a href="http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext">
1449      * http://svn.apache.org/repos/asf/infrastructure/site-tools/trunk/projects/asfext</a>
1450      * @see <a href="http://projects.apache.org/docs/standards.html">http://projects.apache.org/docs/standards.html</a>
1451      */
1452     private void writeASFImplements( XMLWriter writer )
1453     {
1454         if ( asfExtOptions.getStandards() == null || asfExtOptions.getStandards().isEmpty() )
1455         {
1456             return;
1457         }
1458 
1459         for ( Iterator it = asfExtOptions.getStandards().iterator(); it.hasNext(); )
1460         {
1461             Standard standard = (Standard) it.next();
1462 
1463             writer.startElement( "asfext:implements" );
1464             writer.startElement( "asfext:Standard" );
1465 
1466             if ( StringUtils.isEmpty( standard.getTitle() ) )
1467             {
1468                 getLog().warn(
1469                                "No title specified! According http://projects.apache.org/docs/standards.html, "
1470                                    + "asfext:title is required" );
1471             }
1472             else
1473             {
1474                 writer.startElement( "asfext:title" );
1475                 writer.writeText( standard.getTitle() );
1476                 writer.endElement(); // asfext:title
1477             }
1478 
1479             if ( StringUtils.isEmpty( standard.getBody() ) )
1480             {
1481                 getLog().warn(
1482                                "No body specified! According http://projects.apache.org/docs/standards.html, "
1483                                    + "asfext:body is required" );
1484             }
1485             else
1486             {
1487                 writer.startElement( "asfext:body" );
1488                 writer.writeText( standard.getBody() );
1489                 writer.endElement(); // asfext:body
1490             }
1491 
1492             if ( StringUtils.isEmpty( standard.getId() ) )
1493             {
1494                 getLog().warn(
1495                                "No id specified! According http://projects.apache.org/docs/standards.html, "
1496                                    + "asfext:id is required" );
1497             }
1498             else
1499             {
1500                 writer.startElement( "asfext:id" );
1501                 writer.writeText( standard.getId() );
1502                 writer.endElement(); // asfext:id
1503             }
1504 
1505             if ( StringUtils.isNotEmpty( standard.getUrl() ) )
1506             {
1507                 writer.startElement( "asfext:url" );
1508                 writer.writeText( standard.getUrl() );
1509                 writer.endElement(); // asfext:url
1510             }
1511 
1512             writer.endElement(); // asfext:Standard
1513             writer.endElement(); // asfext:implements
1514         }
1515     }
1516 
1517     // ----------------------------------------------------------------------
1518     // Static methods
1519     // ----------------------------------------------------------------------
1520 
1521     /**
1522      * Compose a URL from two parts: a base URL and a file path. This method
1523      * makes sure that there will not be two slash '/' characters after each
1524      * other.
1525      *
1526      * @param base The base URL
1527      * @param path The file
1528      * @return the url with base and path
1529      */
1530     private static String composeUrl( String base, String path )
1531     {
1532         if ( base.endsWith( "/" ) && path.startsWith( "/" ) )
1533         {
1534             return base + path.substring( 1 );
1535         }
1536 
1537         return base + path;
1538     }
1539 
1540     /**
1541      * Convenience method that return true is the defined <code>SCM repository</code> is a known provider.
1542      * <p>
1543      * Actually, we fully support Clearcase, CVS, Perforce, Starteam, SVN by the maven-scm-providers component.
1544      * </p>
1545      *
1546      * @param scmRepository a SCM repository
1547      * @param scmProvider a SCM provider name
1548      * @return true if the provider of the given SCM repository is equal to the given scm provider.
1549      * @see <a href="http://svn.apache.org/repos/asf/maven/scm/trunk/maven-scm-providers/">maven-scm-providers</a>
1550      */
1551     private static boolean isScmSystem( ScmRepository scmRepository, String scmProvider )
1552     {
1553         if ( StringUtils.isEmpty( scmProvider ) )
1554         {
1555             return false;
1556         }
1557 
1558         if ( scmRepository != null && scmProvider.equalsIgnoreCase( scmRepository.getProvider() ) )
1559         {
1560             return true;
1561         }
1562 
1563         return false;
1564     }
1565 }