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