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