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