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