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