View Javadoc
1   package org.apache.maven.doxia.tools;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.Reader;
26  import java.io.StringReader;
27  import java.io.StringWriter;
28  import java.net.MalformedURLException;
29  import java.net.URL;
30  import java.util.AbstractMap;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Collections;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.Properties;
38  import java.util.StringTokenizer;
39  
40  import org.apache.commons.io.FilenameUtils;
41  import org.apache.maven.artifact.Artifact;
42  import org.apache.maven.artifact.factory.ArtifactFactory;
43  import org.apache.maven.artifact.repository.ArtifactRepository;
44  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
45  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
46  import org.apache.maven.artifact.resolver.ArtifactResolver;
47  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
48  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
49  import org.apache.maven.artifact.versioning.VersionRange;
50  import org.apache.maven.doxia.site.decoration.Banner;
51  import org.apache.maven.doxia.site.decoration.DecorationModel;
52  import org.apache.maven.doxia.site.decoration.Menu;
53  import org.apache.maven.doxia.site.decoration.MenuItem;
54  import org.apache.maven.doxia.site.decoration.Skin;
55  import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler;
56  import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader;
57  import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer;
58  import org.apache.maven.model.DistributionManagement;
59  import org.apache.maven.model.Model;
60  import org.apache.maven.model.Site;
61  import org.apache.maven.project.MavenProject;
62  import org.apache.maven.project.MavenProjectBuilder;
63  import org.apache.maven.project.ProjectBuildingException;
64  import org.apache.maven.reporting.MavenReport;
65  import org.codehaus.plexus.component.annotations.Component;
66  import org.codehaus.plexus.component.annotations.Requirement;
67  import org.codehaus.plexus.i18n.I18N;
68  import org.codehaus.plexus.logging.AbstractLogEnabled;
69  import org.codehaus.plexus.util.IOUtil;
70  import org.codehaus.plexus.util.ReaderFactory;
71  import org.codehaus.plexus.util.StringUtils;
72  import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
73  import org.codehaus.plexus.interpolation.InterpolationException;
74  import org.codehaus.plexus.interpolation.MapBasedValueSource;
75  import org.codehaus.plexus.interpolation.ObjectBasedValueSource;
76  import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
77  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
78  
79  /**
80   * Default implementation of the site tool.
81   *
82   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
83   * @version $Id$
84   */
85  @Component( role = SiteTool.class )
86  public class DefaultSiteTool
87      extends AbstractLogEnabled
88      implements SiteTool
89  {
90      // ----------------------------------------------------------------------
91      // Components
92      // ----------------------------------------------------------------------
93  
94      /**
95       * The component that is used to resolve additional artifacts required.
96       */
97      @Requirement
98      private ArtifactResolver artifactResolver;
99  
100     /**
101      * The component used for creating artifact instances.
102      */
103     @Requirement
104     private ArtifactFactory artifactFactory;
105 
106     /**
107      * Internationalization.
108      */
109     @Requirement
110     protected I18N i18n;
111 
112     /**
113      * The component for assembling inheritance.
114      */
115     @Requirement
116     protected DecorationModelInheritanceAssembler assembler;
117 
118     /**
119      * Project builder.
120      */
121     @Requirement
122     protected MavenProjectBuilder mavenProjectBuilder;
123 
124     // ----------------------------------------------------------------------
125     // Public methods
126     // ----------------------------------------------------------------------
127 
128     /** {@inheritDoc} */
129     public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository,
130                                                    List<ArtifactRepository> remoteArtifactRepositories,
131                                                    DecorationModel decoration )
132         throws SiteToolException
133     {
134         checkNotNull( "localRepository", localRepository );
135         checkNotNull( "remoteArtifactRepositories", remoteArtifactRepositories );
136         checkNotNull( "decoration", decoration );
137 
138         Skin skin = decoration.getSkin();
139 
140         if ( skin == null )
141         {
142             skin = Skin.getDefaultSkin();
143         }
144 
145         String version = skin.getVersion();
146         Artifact artifact;
147         try
148         {
149             if ( version == null )
150             {
151                 version = Artifact.RELEASE_VERSION;
152             }
153             VersionRange versionSpec = VersionRange.createFromVersionSpec( version );
154             artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec,
155                                                                  "jar", null, null );
156 
157             artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository );
158         }
159         catch ( InvalidVersionSpecificationException e )
160         {
161             throw new SiteToolException( "InvalidVersionSpecificationException: The skin version '" + version
162                 + "' is not valid: " + e.getMessage(), e );
163         }
164         catch ( ArtifactResolutionException e )
165         {
166             throw new SiteToolException( "ArtifactResolutionException: Unable to find skin", e );
167         }
168         catch ( ArtifactNotFoundException e )
169         {
170             throw new SiteToolException( "ArtifactNotFoundException: The skin does not exist: " + e.getMessage(), e );
171         }
172 
173         return artifact;
174     }
175 
176     /** {@inheritDoc} */
177     public Artifact getDefaultSkinArtifact( ArtifactRepository localRepository,
178                                             List<ArtifactRepository> remoteArtifactRepositories )
179         throws SiteToolException
180     {
181         return getSkinArtifactFromRepository( localRepository, remoteArtifactRepositories, new DecorationModel() );
182     }
183 
184     /** {@inheritDoc} */
185     public String getRelativePath( String to, String from )
186     {
187         checkNotNull( "to", to );
188         checkNotNull( "from", from );
189 
190         URL toUrl = null;
191         URL fromUrl = null;
192 
193         String toPath = to;
194         String fromPath = from;
195 
196         try
197         {
198             toUrl = new URL( to );
199         }
200         catch ( MalformedURLException e )
201         {
202             try
203             {
204                 toUrl = new File( getNormalizedPath( to ) ).toURI().toURL();
205             }
206             catch ( MalformedURLException e1 )
207             {
208                 getLogger().warn( "Unable to load a URL for '" + to + "': " + e.getMessage() );
209             }
210         }
211 
212         try
213         {
214             fromUrl = new URL( from );
215         }
216         catch ( MalformedURLException e )
217         {
218             try
219             {
220                 fromUrl = new File( getNormalizedPath( from ) ).toURI().toURL();
221             }
222             catch ( MalformedURLException e1 )
223             {
224                 getLogger().warn( "Unable to load a URL for '" + from + "': " + e.getMessage() );
225             }
226         }
227 
228         if ( toUrl != null && fromUrl != null )
229         {
230             // URLs, determine if they share protocol and domain info
231 
232             if ( ( toUrl.getProtocol().equalsIgnoreCase( fromUrl.getProtocol() ) )
233                 && ( toUrl.getHost().equalsIgnoreCase( fromUrl.getHost() ) )
234                 && ( toUrl.getPort() == fromUrl.getPort() ) )
235             {
236                 // shared URL domain details, use URI to determine relative path
237 
238                 toPath = toUrl.getFile();
239                 fromPath = fromUrl.getFile();
240             }
241             else
242             {
243                 // don't share basic URL information, no relative available
244 
245                 return to;
246             }
247         }
248         else if ( ( toUrl != null && fromUrl == null ) || ( toUrl == null && fromUrl != null ) )
249         {
250             // one is a URL and the other isn't, no relative available.
251 
252             return to;
253         }
254 
255         // either the two locations are not URLs or if they are they
256         // share the common protocol and domain info and we are left
257         // with their URI information
258 
259         String relativePath = getRelativeFilePath( fromPath, toPath );
260 
261         if ( relativePath == null )
262         {
263             relativePath = to;
264         }
265 
266         if ( getLogger().isDebugEnabled() && !relativePath.toString().equals( to ) )
267         {
268             getLogger().debug( "Mapped url: " + to + " to relative path: " + relativePath );
269         }
270 
271         return relativePath;
272     }
273 
274     private static String getRelativeFilePath( final String oldPath, final String newPath )
275     {
276         // normalize the path delimiters
277 
278         String fromPath = new File( oldPath ).getPath();
279         String toPath = new File( newPath ).getPath();
280 
281         // strip any leading slashes if its a windows path
282         if ( toPath.matches( "^\\[a-zA-Z]:" ) )
283         {
284             toPath = toPath.substring( 1 );
285         }
286         if ( fromPath.matches( "^\\[a-zA-Z]:" ) )
287         {
288             fromPath = fromPath.substring( 1 );
289         }
290 
291         // lowercase windows drive letters.
292         if ( fromPath.startsWith( ":", 1 ) )
293         {
294             fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 );
295         }
296         if ( toPath.startsWith( ":", 1 ) )
297         {
298             toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 );
299         }
300 
301         // check for the presence of windows drives. No relative way of
302         // traversing from one to the other.
303 
304         if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) )
305             && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) )
306         {
307             // they both have drive path element but they don't match, no
308             // relative path
309 
310             return null;
311         }
312 
313         if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) )
314             || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) )
315         {
316 
317             // one has a drive path element and the other doesn't, no relative
318             // path.
319 
320             return null;
321 
322         }
323 
324         final String relativePath = buildRelativePath( toPath, fromPath, File.separatorChar );
325 
326         return relativePath.toString();
327     }
328 
329     /** {@inheritDoc} */
330     public File getSiteDescriptor( File siteDirectory, Locale locale )
331     {
332         checkNotNull( "siteDirectory", siteDirectory );
333         final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale;
334 
335         File siteDescriptor = new File( siteDirectory, "site_" + llocale.getLanguage() + ".xml" );
336 
337         if ( !siteDescriptor.isFile() )
338         {
339             siteDescriptor = new File( siteDirectory, "site.xml" );
340         }
341         return siteDescriptor;
342     }
343 
344     /**
345      * Get a site descriptor from one of the repositories.
346      *
347      * @param project the Maven project, not null.
348      * @param localRepository the Maven local repository, not null.
349      * @param repositories the Maven remote repositories, not null.
350      * @param locale the locale wanted for the site descriptor. If not null, searching for
351      * <code>site_<i>localeLanguage</i>.xml</code>, otherwise searching for <code>site.xml</code>.
352      * @return the site descriptor into the local repository after download of it from repositories or null if not
353      * found in repositories.
354      * @throws SiteToolException if any
355      */
356     File getSiteDescriptorFromRepository( MavenProject project, ArtifactRepository localRepository,
357                                                  List<ArtifactRepository> repositories, Locale locale )
358         throws SiteToolException
359     {
360         checkNotNull( "project", project );
361         checkNotNull( "localRepository", localRepository );
362         checkNotNull( "repositories", repositories );
363 
364         final Locale llocale = ( locale == null ) ? new Locale( "" ) : locale;
365 
366         try
367         {
368             return resolveSiteDescriptor( project, localRepository, repositories, llocale );
369         }
370         catch ( ArtifactNotFoundException e )
371         {
372             getLogger().debug( "ArtifactNotFoundException: Unable to locate site descriptor: " + e );
373             return null;
374         }
375         catch ( ArtifactResolutionException e )
376         {
377             throw new SiteToolException( "ArtifactResolutionException: Unable to locate site descriptor: "
378                 + e.getMessage(), e );
379         }
380         catch ( IOException e )
381         {
382             throw new SiteToolException( "IOException: Unable to locate site descriptor: " + e.getMessage(), e );
383         }
384     }
385 
386     /**
387      * Read site descriptor content from Reader, adding support for deprecated <code>${reports}</code>,
388      * <code>${parentProject}</code> and <code>${modules}</code> tags.
389      *
390      * @param reader
391      * @return the input content interpolated with deprecated tags 
392      * @throws IOException
393      */
394     private static String readSiteDescriptor( Reader reader )
395         throws IOException
396     {
397         String siteDescriptorContent = IOUtil.toString( reader );
398     
399         // This is to support the deprecated ${reports}, ${parentProject} and ${modules} tags.
400         Properties props = new Properties();
401         props.put( "reports", "<menu ref=\"reports\"/>\n" );
402         props.put( "modules", "<menu ref=\"modules\"/>\n" );
403         props.put( "parentProject", "<menu ref=\"parent\"/>" );
404     
405         return StringUtils.interpolate( siteDescriptorContent, props );
406     }
407 
408     /** {@inheritDoc} */
409     public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project,
410                                                List<MavenProject> reactorProjects, ArtifactRepository localRepository,
411                                                List<ArtifactRepository> repositories )
412         throws SiteToolException
413     {
414         checkNotNull( "project", project );
415         checkNotNull( "reactorProjects", reactorProjects );
416         checkNotNull( "localRepository", localRepository );
417         checkNotNull( "repositories", repositories );
418 
419         final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale;
420 
421         getLogger().debug( "Computing decoration model of " + project.getId() + " for locale " + llocale );
422 
423         Map.Entry<DecorationModel, MavenProject> result =
424             getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories );
425         DecorationModel decorationModel = result.getKey();
426         MavenProject parentProject = result.getValue();
427 
428         if ( decorationModel == null )
429         {
430             getLogger().debug( "Using default site descriptor" );
431 
432             String siteDescriptorContent;
433 
434             Reader reader = null;
435             try
436             {
437                 // Note the default is not a super class - it is used when nothing else is found
438                 reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) );
439                 siteDescriptorContent = readSiteDescriptor( reader );
440             }
441             catch ( IOException e )
442             {
443                 throw new SiteToolException( "Error reading default site descriptor: " + e.getMessage(), e );
444             }
445             finally
446             {
447                 IOUtil.close( reader );
448             }
449 
450             decorationModel = readDecorationModel( siteDescriptorContent );
451         }
452 
453         // DecorationModel back to String to interpolate, then go back to DecorationModel
454         String siteDescriptorContent = decorationModelToString( decorationModel );
455 
456         siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent );
457 
458         decorationModel = readDecorationModel( siteDescriptorContent );
459 
460         if ( parentProject != null )
461         {
462             populateParentMenu( decorationModel, llocale, project, parentProject, true );
463         }
464 
465         populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true );
466 
467         if ( decorationModel.getBannerLeft() == null )
468         {
469             // extra default to set
470             Banner banner = new Banner();
471             banner.setName( project.getName() );
472             decorationModel.setBannerLeft( banner );
473         }
474 
475         return decorationModel;
476     }
477 
478     /** {@inheritDoc} */
479     public String getInterpolatedSiteDescriptorContent( Map<String, String> props, MavenProject aProject,
480                                                         String siteDescriptorContent )
481         throws SiteToolException
482     {
483         checkNotNull( "props", props );
484 
485         return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent );
486     }
487 
488     private String getInterpolatedSiteDescriptorContent( MavenProject aProject,
489                                                         String siteDescriptorContent )
490         throws SiteToolException
491     {
492         checkNotNull( "aProject", aProject );
493         checkNotNull( "siteDescriptorContent", siteDescriptorContent );
494 
495         RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
496 
497         try
498         {
499             interpolator.addValueSource( new EnvarBasedValueSource() );
500         }
501         catch ( IOException e )
502         {
503             // Prefer logging?
504             throw new SiteToolException( "IOException: cannot interpolate environment properties: " + e.getMessage(),
505                                          e );
506         }
507 
508         interpolator.addValueSource( new ObjectBasedValueSource( aProject ) );
509 
510         interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) );
511 
512         try
513         {
514             // FIXME: this does not escape xml entities, see MSITE-226, PLXCOMP-118
515             return interpolator.interpolate( siteDescriptorContent, "project" );
516         }
517         catch ( InterpolationException e )
518         {
519             throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e );
520         }
521     }
522 
523     /** {@inheritDoc} */
524     public MavenProject getParentProject( MavenProject aProject, List<MavenProject> reactorProjects,
525                                           ArtifactRepository localRepository )
526     {
527         checkNotNull( "aProject", aProject );
528         checkNotNull( "reactorProjects", reactorProjects );
529         checkNotNull( "localRepository", localRepository );
530 
531         if ( isMaven3OrMore() )
532         {
533             // no need to make voodoo with Maven 3: job already done
534             return aProject.getParent();
535         }
536 
537         MavenProject parentProject = null;
538 
539         MavenProject origParent = aProject.getParent();
540         if ( origParent != null )
541         {
542             for ( MavenProject reactorProject : reactorProjects )
543             {
544                 if ( reactorProject.getGroupId().equals( origParent.getGroupId() )
545                     && reactorProject.getArtifactId().equals( origParent.getArtifactId() )
546                     && reactorProject.getVersion().equals( origParent.getVersion() ) )
547                 {
548                     parentProject = reactorProject;
549 
550                     getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" );
551                     break;
552                 }
553             }
554 
555             if ( parentProject == null && aProject.getBasedir() != null
556                 && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) )
557             {
558                 try
559                 {
560                     String relativePath = aProject.getModel().getParent().getRelativePath();
561 
562                     File pomFile = new File( aProject.getBasedir(), relativePath );
563 
564                     if ( pomFile.isDirectory() )
565                     {
566                         pomFile = new File( pomFile, "pom.xml" );
567                     }
568                     pomFile = new File( getNormalizedPath( pomFile.getPath() ) );
569 
570                     if ( pomFile.isFile() )
571                     {
572                         MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null );
573 
574                         if ( mavenProject.getGroupId().equals( origParent.getGroupId() )
575                             && mavenProject.getArtifactId().equals( origParent.getArtifactId() )
576                             && mavenProject.getVersion().equals( origParent.getVersion() ) )
577                         {
578                             parentProject = mavenProject;
579 
580                             getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: "
581                                 + relativePath );
582                         }
583                     }
584                 }
585                 catch ( ProjectBuildingException e )
586                 {
587                     getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: "
588                         + e.getMessage() );
589                 }
590             }
591 
592             if ( parentProject == null )
593             {
594                 try
595                 {
596                     parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject
597                         .getRemoteArtifactRepositories(), localRepository );
598 
599                     getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" );
600                 }
601                 catch ( ProjectBuildingException e )
602                 {
603                     getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: "
604                         + e.getMessage() );
605                 }
606             }
607 
608             if ( parentProject == null )
609             {
610                 // fallback to original parent, which may contain uninterpolated value (still need a unit test)
611 
612                 parentProject = origParent;
613 
614                 getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" );
615             }
616         }
617         return parentProject;
618     }
619 
620     /**
621      * Populate the pre-defined <code>parent</code> menu of the decoration model,
622      * if used through <code>&lt;menu ref="parent"/&gt;</code>.
623      *
624      * @param decorationModel the Doxia Sitetools DecorationModel, not null.
625      * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm.
626      * @param project a Maven project, not null.
627      * @param parentProject a Maven parent project, not null.
628      * @param keepInheritedRefs used for inherited references.
629      */
630     private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project,
631                                     MavenProject parentProject, boolean keepInheritedRefs )
632     {
633         checkNotNull( "decorationModel", decorationModel );
634         checkNotNull( "project", project );
635         checkNotNull( "parentProject", parentProject );
636 
637         Menu menu = decorationModel.getMenuRef( "parent" );
638 
639         if ( menu == null )
640         {
641             return;
642         }
643 
644         if ( keepInheritedRefs && menu.isInheritAsRef() )
645         {
646             return;
647         }
648 
649         final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale;
650 
651         String parentUrl = getDistMgmntSiteUrl( parentProject );
652 
653         if ( parentUrl != null )
654         {
655             if ( parentUrl.endsWith( "/" ) )
656             {
657                 parentUrl += "index.html";
658             }
659             else
660             {
661                 parentUrl += "/index.html";
662             }
663 
664             parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) );
665         }
666         else
667         {
668             // parent has no url, assume relative path is given by site structure
669             File parentBasedir = parentProject.getBasedir();
670             // First make sure that the parent is available on the file system
671             if ( parentBasedir != null )
672             {
673                 // Try to find the relative path to the parent via the file system
674                 String parentPath = parentBasedir.getAbsolutePath();
675                 String projectPath = project.getBasedir().getAbsolutePath();
676                 parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html";
677             }
678         }
679 
680         // Only add the parent menu if we were able to find a URL for it
681         if ( parentUrl == null )
682         {
683             getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." );
684         }
685         else
686         {
687             if ( menu.getName() == null )
688             {
689                 menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) );
690             }
691 
692             MenuItem item = new MenuItem();
693             item.setName( parentProject.getName() );
694             item.setHref( parentUrl );
695             menu.addItem( item );
696         }
697     }
698 
699     /**
700      * Populate the pre-defined <code>modules</code> menu of the decoration model,
701      * if used through <code>&lt;menu ref="modules"/&gt;</code>.
702      *
703      * @param decorationModel the Doxia Sitetools DecorationModel, not null.
704      * @param locale the locale used for the i18n in DecorationModel. If null, using the default locale in the jvm.
705      * @param project a Maven project, not null.
706      * @param reactorProjects the Maven reactor projects, not null.
707      * @param localRepository the Maven local repository, not null.
708      * @param keepInheritedRefs used for inherited references.
709      * @throws SiteToolException if any
710      */
711     private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project,
712                                      List<MavenProject> reactorProjects, ArtifactRepository localRepository,
713                                      boolean keepInheritedRefs )
714         throws SiteToolException
715     {
716         checkNotNull( "project", project );
717         checkNotNull( "reactorProjects", reactorProjects );
718         checkNotNull( "localRepository", localRepository );
719         checkNotNull( "decorationModel", decorationModel );
720 
721         Menu menu = decorationModel.getMenuRef( "modules" );
722 
723         if ( menu == null )
724         {
725             return;
726         }
727 
728         if ( keepInheritedRefs && menu.isInheritAsRef() )
729         {
730             return;
731         }
732 
733         final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ;
734 
735         // we require child modules and reactors to process module menu
736         if ( project.getModules().size() > 0 )
737         {
738             if ( menu.getName() == null )
739             {
740                 menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) );
741             }
742 
743             getLogger().debug( "Attempting to load module information from local filesystem" );
744 
745             // Not running reactor - search for the projects manually
746             List<Model> models = new ArrayList<Model>( project.getModules().size() );
747             for ( String module : (List<String>) project.getModules() )
748             {
749                 Model model;
750                 File f = new File( project.getBasedir(), module + "/pom.xml" );
751                 if ( f.exists() )
752                 {
753                     try
754                     {
755                         model = mavenProjectBuilder.build( f, localRepository, null ).getModel();
756                     }
757                     catch ( ProjectBuildingException e )
758                     {
759                         throw new SiteToolException( "Unable to read local module-POM", e );
760                     }
761                 }
762                 else
763                 {
764                     getLogger().warn( "No filesystem module-POM available" );
765 
766                     model = new Model();
767                     model.setName( module );
768                     setDistMgmntSiteUrl( model, module );
769                 }
770                 models.add( model );
771             }
772             populateModulesMenuItemsFromModels( project, models, menu );
773         }
774         else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null )
775         {
776             // only remove if project has no modules AND menu is not inherited, see MSHARED-174
777             decorationModel.removeMenuRef( "modules" );
778         }
779     }
780 
781     /** {@inheritDoc} */
782     public void populateReportsMenu( DecorationModel decorationModel, Locale locale,
783                                      Map<String, List<MavenReport>> categories )
784     {
785         checkNotNull( "decorationModel", decorationModel );
786         checkNotNull( "categories", categories );
787 
788         Menu menu = decorationModel.getMenuRef( "reports" );
789 
790         if ( menu == null )
791         {
792             return;
793         }
794 
795         final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale;
796 
797         if ( menu.getName() == null )
798         {
799             menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) );
800         }
801 
802         boolean found = false;
803         if ( menu.getItems().isEmpty() )
804         {
805             List<MavenReport> categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION );
806             if ( !isEmptyList( categoryReports ) )
807             {
808                 MenuItem item = createCategoryMenu(
809                                                     i18n.getString( "site-tool", llocale,
810                                                                     "decorationModel.menu.projectinformation" ),
811                                                     "/project-info.html", categoryReports, llocale );
812                 menu.getItems().add( item );
813                 found = true;
814             }
815 
816             categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS );
817             if ( !isEmptyList( categoryReports ) )
818             {
819                 MenuItem item =
820                     createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ),
821                                         "/project-reports.html", categoryReports, llocale );
822                 menu.getItems().add( item );
823                 found = true;
824             }
825         }
826         if ( !found )
827         {
828             decorationModel.removeMenuRef( "reports" );
829         }
830     }
831 
832     /** {@inheritDoc} */
833     public List<Locale> getSiteLocales( String locales )
834     {
835         if ( locales == null )
836         {
837             return Collections.singletonList( DEFAULT_LOCALE );
838         }
839 
840         String[] localesArray = StringUtils.split( locales, "," );
841         List<Locale> localesList = new ArrayList<Locale>( localesArray.length );
842 
843         for ( String localeString : localesArray )
844         {
845             Locale locale = codeToLocale( localeString );
846 
847             if ( locale == null )
848             {
849                 continue;
850             }
851 
852             if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) )
853             {
854                 if ( getLogger().isWarnEnabled() )
855                 {
856                     getLogger().warn( "The locale defined by '" + locale
857                         + "' is not available in this Java Virtual Machine ("
858                         + System.getProperty( "java.version" )
859                         + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" );
860                 }
861                 continue;
862             }
863 
864             // Default bundles are in English
865             if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) )
866                 && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage()
867                     .equals( locale.getLanguage() ) ) )
868             {
869                 if ( getLogger().isWarnEnabled() )
870                 {
871                     getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH )
872                         + ") is not currently supported by Maven Site - IGNORING."
873                         + "\nContributions are welcome and greatly appreciated!"
874                         + "\nIf you want to contribute a new translation, please visit "
875                         + "http://maven.apache.org/plugins/localization.html for detailed instructions." );
876                 }
877 
878                 continue;
879             }
880 
881             localesList.add( locale );
882         }
883 
884         if ( localesList.isEmpty() )
885         {
886             localesList = Collections.singletonList( DEFAULT_LOCALE );
887         }
888 
889         return localesList;
890     }
891 
892     /**
893      * Converts a locale code like "en", "en_US" or "en_US_win" to a <code>java.util.Locale</code>
894      * object.
895      * <p>If localeCode = <code>default</code>, return the current value of the default locale for this instance
896      * of the Java Virtual Machine.</p>
897      *
898      * @param localeCode the locale code string.
899      * @return a java.util.Locale object instanced or null if errors occurred
900      * @see <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/Locale.html">java.util.Locale#getDefault()</a>
901      */
902     private Locale codeToLocale( String localeCode )
903     {
904         if ( localeCode == null )
905         {
906             return null;
907         }
908 
909         if ( "default".equalsIgnoreCase( localeCode ) )
910         {
911             return Locale.getDefault();
912         }
913 
914         String language = "";
915         String country = "";
916         String variant = "";
917 
918         StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" );
919         final int maxTokens = 3;
920         if ( tokenizer.countTokens() > maxTokens )
921         {
922             if ( getLogger().isWarnEnabled() )
923             {
924                 getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" );
925             }
926             return null;
927         }
928 
929         if ( tokenizer.hasMoreTokens() )
930         {
931             language = tokenizer.nextToken();
932             if ( tokenizer.hasMoreTokens() )
933             {
934                 country = tokenizer.nextToken();
935                 if ( tokenizer.hasMoreTokens() )
936                 {
937                     variant = tokenizer.nextToken();
938                 }
939             }
940         }
941 
942         return new Locale( language, country, variant );
943     }
944 
945     // ----------------------------------------------------------------------
946     // Protected methods
947     // ----------------------------------------------------------------------
948 
949     /**
950      * @param path could be null.
951      * @return the path normalized, i.e. by eliminating "/../" and "/./" in the path.
952      * @see FilenameUtils#normalize(String)
953      */
954     protected static String getNormalizedPath( String path )
955     {
956         String normalized = FilenameUtils.normalize( path );
957         if ( normalized == null )
958         {
959             normalized = path;
960         }
961         return ( normalized == null ) ? null : normalized.replace( '\\', '/' );
962     }
963 
964     // ----------------------------------------------------------------------
965     // Private methods
966     // ----------------------------------------------------------------------
967 
968     /**
969      * @param project not null
970      * @param localRepository not null
971      * @param repositories not null
972      * @param locale not null
973      * @return the resolved site descriptor
974      * @throws IOException if any
975      * @throws ArtifactResolutionException if any
976      * @throws ArtifactNotFoundException if any
977      */
978     private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository,
979                                         List<ArtifactRepository> repositories, Locale locale )
980         throws IOException, ArtifactResolutionException, ArtifactNotFoundException
981     {
982         File result;
983 
984         // TODO: this is a bit crude - proper type, or proper handling as metadata rather than an artifact in 2.1?
985         Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(),
986                                                                           project.getArtifactId(),
987                                                                           project.getVersion(), "xml",
988                                                                           "site_" + locale.getLanguage() );
989 
990         boolean found = false;
991         try
992         {
993             artifactResolver.resolve( artifact, repositories, localRepository );
994 
995             result = artifact.getFile();
996 
997             // we use zero length files to avoid re-resolution (see below)
998             if ( result.length() > 0 )
999             {
1000                 found = true;
1001             }
1002             else
1003             {
1004                 getLogger().debug( "No site descriptor found for " + project.getId() + " for locale "
1005                     + locale.getLanguage() );
1006             }
1007         }
1008         catch ( ArtifactNotFoundException e )
1009         {
1010             getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e );
1011 
1012             // we can afford to write an empty descriptor here as we don't expect it to turn up later in the remote
1013             // repository, because the parent was already released (and snapshots are updated automatically if changed)
1014             result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) );
1015             result.getParentFile().mkdirs();
1016             result.createNewFile();
1017         }
1018 
1019         if ( !found )
1020         {
1021             artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(),
1022                                                                      project.getVersion(), "xml", "site" );
1023             try
1024             {
1025                 artifactResolver.resolve( artifact, repositories, localRepository );
1026             }
1027             catch ( ArtifactNotFoundException e )
1028             {
1029                 // see above regarding this zero length file
1030                 result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) );
1031                 result.getParentFile().mkdirs();
1032                 result.createNewFile();
1033 
1034                 throw e;
1035             }
1036 
1037             result = artifact.getFile();
1038 
1039             // we use zero length files to avoid re-resolution (see below)
1040             if ( result.length() == 0 )
1041             {
1042                 getLogger().debug( "No site descriptor found for " + project.getId() + " without locale" );
1043                 result = null;
1044             }
1045         }
1046 
1047         return result;
1048     }
1049 
1050     /**
1051      * @param depth depth of project
1052      * @param siteDirectory, can be null if project.basedir is null, ie POM from repository
1053      * @param locale not null
1054      * @param project not null
1055      * @param reactorProjects not null
1056      * @param localRepository not null
1057      * @param repositories not null
1058      * @param origProps not null
1059      * @return the decoration model depending the locale and the parent project
1060      * @throws SiteToolException if any
1061      */
1062     private Map.Entry<DecorationModel, MavenProject> getDecorationModel( int depth, File siteDirectory, Locale locale,
1063                                                                          MavenProject project,
1064                                                                          List<MavenProject> reactorProjects,
1065                                                                          ArtifactRepository localRepository,
1066                                                                          List<ArtifactRepository> repositories )
1067         throws SiteToolException
1068     {
1069         // 1. get site descriptor File
1070         File siteDescriptor;
1071         if ( project.getBasedir() == null )
1072         {
1073             // POM is in the repository: look into the repository for site descriptor
1074             try
1075             {
1076                 siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale );
1077             }
1078             catch ( SiteToolException e )
1079             {
1080                 throw new SiteToolException( "The site descriptor cannot be resolved from the repository: "
1081                     + e.getMessage(), e );
1082             }
1083         }
1084         else
1085         {
1086             // POM is in build directory: look for site descriptor as local file
1087             siteDescriptor = getSiteDescriptor( siteDirectory, locale );
1088         }
1089 
1090         // 2. read DecorationModel from site descriptor File
1091         DecorationModel decoration = null;
1092         Reader siteDescriptorReader = null;
1093         try
1094         {
1095             if ( siteDescriptor != null && siteDescriptor.exists() )
1096             {
1097                 getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) )
1098                     + " site descriptor from " + siteDescriptor );
1099 
1100                 siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor );
1101 
1102                 String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader );
1103 
1104                 decoration = readDecorationModel( siteDescriptorContent );
1105                 decoration.setLastModified( siteDescriptor.lastModified() );
1106             }
1107             else
1108             {
1109                 getLogger().debug( "No site descriptor found for " + project.getId() );
1110             }
1111         }
1112         catch ( IOException e )
1113         {
1114             throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from "
1115                 + siteDescriptor, e );
1116         }
1117         finally
1118         {
1119             IOUtil.close( siteDescriptorReader );
1120         }
1121 
1122         // 3. look for parent project
1123         MavenProject parentProject = getParentProject( project, reactorProjects, localRepository ); 
1124 
1125         // 4. merge with parent project DecorationModel
1126         if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) )
1127         {
1128             depth++;
1129             getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: "
1130                 + parentProject.getId() );
1131 
1132             File parentSiteDirectory = null;
1133             if ( parentProject.getBasedir() != null )
1134             {
1135                 // extrapolate parent project site directory
1136                 String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(),
1137                                                                siteDescriptor.getParentFile().getAbsolutePath() );
1138 
1139                 parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath );
1140                 // notice: using same siteRelativePath for parent as current project; may be wrong if site plugin
1141                 // has different configuration. But this is a rare case (this only has impact if parent is from reactor)
1142             }
1143 
1144             DecorationModel parentDecoration =
1145                 getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository,
1146                                     repositories ).getKey();
1147 
1148             // MSHARED-116 requires an empty decoration model (instead of a null one)
1149             // MSHARED-145 requires us to do this only if there is a parent to merge it with
1150             if ( decoration == null && parentDecoration != null )
1151             {
1152                 // we have no site descriptor: merge the parent into an empty one
1153                 decoration = new DecorationModel();
1154             }
1155 
1156             String name = project.getName();
1157             if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) )
1158             {
1159                 name = decoration.getName();
1160             }
1161 
1162             // Merge the parent and child DecorationModels
1163             String projectDistMgmnt = getDistMgmntSiteUrl( project );
1164             String parentDistMgmnt = getDistMgmntSiteUrl( parentProject );
1165             if ( getLogger().isDebugEnabled() )
1166             {
1167                 getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth
1168                     + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = "
1169                     + parentDistMgmnt );
1170             }
1171             assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt,
1172                                                 parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt );
1173         }
1174 
1175         return new AbstractMap.SimpleEntry<DecorationModel, MavenProject>( decoration, parentProject );
1176     }
1177 
1178     /**
1179      * @param siteDescriptorContent not null
1180      * @return the decoration model object
1181      * @throws SiteToolException if any
1182      */
1183     private DecorationModel readDecorationModel( String siteDescriptorContent )
1184         throws SiteToolException
1185     {
1186         try
1187         {
1188             return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) );
1189         }
1190         catch ( XmlPullParserException e )
1191         {
1192             throw new SiteToolException( "Error parsing site descriptor", e );
1193         }
1194         catch ( IOException e )
1195         {
1196             throw new SiteToolException( "Error reading site descriptor", e );
1197         }
1198     }
1199 
1200     private String decorationModelToString( DecorationModel decoration )
1201         throws SiteToolException
1202     {
1203         StringWriter writer = new StringWriter();
1204 
1205         try
1206         {
1207             new DecorationXpp3Writer().write( writer, decoration );
1208             return writer.toString();
1209         }
1210         catch ( IOException e )
1211         {
1212             throw new SiteToolException( "Error reading site descriptor", e );
1213         }
1214         finally
1215         {
1216             IOUtil.close( writer );
1217         }
1218     }
1219 
1220     private static String buildRelativePath( final String toPath,  final String fromPath, final char separatorChar )
1221     {
1222         // use tokenizer to traverse paths and for lazy checking
1223         StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) );
1224         StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) );
1225 
1226         int count = 0;
1227 
1228         // walk along the to path looking for divergence from the from path
1229         while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() )
1230         {
1231             if ( separatorChar == '\\' )
1232             {
1233                 if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) )
1234                 {
1235                     break;
1236                 }
1237             }
1238             else
1239             {
1240                 if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) )
1241                 {
1242                     break;
1243                 }
1244             }
1245 
1246             count++;
1247         }
1248 
1249         // reinitialize the tokenizers to count positions to retrieve the
1250         // gobbled token
1251 
1252         toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) );
1253         fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) );
1254 
1255         while ( count-- > 0 )
1256         {
1257             fromTokeniser.nextToken();
1258             toTokeniser.nextToken();
1259         }
1260 
1261         StringBuilder relativePath = new StringBuilder();
1262 
1263         // add back refs for the rest of from location.
1264         while ( fromTokeniser.hasMoreTokens() )
1265         {
1266             fromTokeniser.nextToken();
1267 
1268             relativePath.append( ".." );
1269 
1270             if ( fromTokeniser.hasMoreTokens() )
1271             {
1272                 relativePath.append( separatorChar );
1273             }
1274         }
1275 
1276         if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() )
1277         {
1278             relativePath.append( separatorChar );
1279         }
1280 
1281         // add fwd fills for whatever's left of to.
1282         while ( toTokeniser.hasMoreTokens() )
1283         {
1284             relativePath.append( toTokeniser.nextToken() );
1285 
1286             if ( toTokeniser.hasMoreTokens() )
1287             {
1288                 relativePath.append( separatorChar );
1289             }
1290         }
1291         return relativePath.toString();
1292     }
1293 
1294     /**
1295      * @param project not null
1296      * @param models not null
1297      * @param menu not null
1298      */
1299     private void populateModulesMenuItemsFromModels( MavenProject project, List<Model> models, Menu menu )
1300     {
1301         for ( Model model : models )
1302         {
1303             String reactorUrl = getDistMgmntSiteUrl( model );
1304             String name = ( model.getName() == null ) ? model.getArtifactId() : model.getName();
1305 
1306             appendMenuItem( project, menu, name, reactorUrl, model.getArtifactId() );
1307         }
1308     }
1309 
1310     /**
1311      * @param project not null
1312      * @param menu not null
1313      * @param name not null
1314      * @param href could be null
1315      * @param defaultHref not null
1316      */
1317     private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref )
1318     {
1319         String selectedHref = href;
1320 
1321         if ( selectedHref == null )
1322         {
1323             selectedHref = defaultHref;
1324         }
1325 
1326         MenuItem item = new MenuItem();
1327         item.setName( name );
1328 
1329         String baseUrl = getDistMgmntSiteUrl( project );
1330         if ( baseUrl != null )
1331         {
1332             selectedHref = getRelativePath( selectedHref, baseUrl );
1333         }
1334 
1335         if ( selectedHref.endsWith( "/" ) )
1336         {
1337             item.setHref( selectedHref + "index.html" );
1338         }
1339         else
1340         {
1341             item.setHref( selectedHref + "/index.html" );
1342         }
1343         menu.addItem( item );
1344     }
1345 
1346     /**
1347      * @param name not null
1348      * @param href not null
1349      * @param categoryReports not null
1350      * @param locale not null
1351      * @return the menu item object
1352      */
1353     private MenuItem createCategoryMenu( String name, String href, List<MavenReport> categoryReports, Locale locale )
1354     {
1355         MenuItem item = new MenuItem();
1356         item.setName( name );
1357         item.setCollapse( true );
1358         item.setHref( href );
1359 
1360         // MSHARED-172, allow reports to define their order in some other way?
1361         //Collections.sort( categoryReports, new ReportComparator( locale ) );
1362 
1363         for ( MavenReport report : categoryReports )
1364         {
1365             MenuItem subitem = new MenuItem();
1366             subitem.setName( report.getName( locale ) );
1367             subitem.setHref( report.getOutputName() + ".html" );
1368             item.getItems().add( subitem );
1369         }
1370 
1371         return item;
1372     }
1373 
1374     // ----------------------------------------------------------------------
1375     // static methods
1376     // ----------------------------------------------------------------------
1377 
1378     /**
1379      * Convenience method.
1380      *
1381      * @param list could be null
1382      * @return true if the list is <code>null</code> or empty
1383      */
1384     private static boolean isEmptyList( List<?> list )
1385     {
1386         return list == null || list.isEmpty();
1387     }
1388 
1389     /**
1390      * Return distributionManagement.site.url if defined, null otherwise.
1391      *
1392      * @param project not null
1393      * @return could be null
1394      */
1395     private static String getDistMgmntSiteUrl( MavenProject project )
1396     {
1397         return getDistMgmntSiteUrl( project.getDistributionManagement() );
1398     }
1399 
1400     /**
1401      * Return distributionManagement.site.url if defined, null otherwise.
1402      *
1403      * @param model not null
1404      * @return could be null
1405      */
1406     private static String getDistMgmntSiteUrl( Model model )
1407     {
1408         return getDistMgmntSiteUrl( model.getDistributionManagement() );
1409     }
1410 
1411     private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt )
1412     {
1413         if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null )
1414         {
1415             return urlEncode( distMgmnt.getSite().getUrl() );
1416         }
1417 
1418         return null;
1419     }
1420 
1421     private static String urlEncode( final String url )
1422     {
1423         if ( url == null )
1424         {
1425             return null;
1426         }
1427 
1428         try
1429         {
1430             return new File( url ).toURI().toURL().toExternalForm();
1431         }
1432         catch ( MalformedURLException ex )
1433         {
1434             return url; // this will then throw somewhere else
1435         }
1436     }
1437 
1438     private static void setDistMgmntSiteUrl( Model model, String url )
1439     {
1440         if ( model.getDistributionManagement() == null )
1441         {
1442             model.setDistributionManagement( new DistributionManagement() );
1443         }
1444 
1445         if ( model.getDistributionManagement().getSite() == null )
1446         {
1447             model.getDistributionManagement().setSite( new Site() );
1448         }
1449 
1450         model.getDistributionManagement().getSite().setUrl( url );
1451     }
1452 
1453     private void checkNotNull( String name, Object value )
1454     {
1455         if ( value == null )
1456         {
1457             throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." );
1458         }
1459     }
1460 
1461     /**
1462      * Check the current Maven version to see if it's Maven 3.0 or newer.
1463      */
1464     private static boolean isMaven3OrMore()
1465     {
1466         return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3;
1467     }
1468 
1469     private static String getMavenVersion()
1470     {
1471         // This relies on the fact that MavenProject is the in core classloader
1472         // and that the core classloader is for the maven-core artifact
1473         // and that should have a pom.properties file
1474         // if this ever changes, we will have to revisit this code.
1475         final Properties properties = new Properties();
1476         final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties";
1477         final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties );
1478        try
1479         {
1480             properties.load( in );
1481         }
1482         catch ( IOException ioe )
1483         {
1484             return "";
1485         }
1486         finally
1487         {
1488             IOUtil.close( in );
1489         }
1490 
1491         return properties.getProperty( "version" ).trim();
1492     }
1493 }