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