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.PrefixedObjectValueSource;
77 import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource;
78 import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
79 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
80
81
82
83
84
85
86
87 @Component( role = SiteTool.class )
88 public class DefaultSiteTool
89 extends AbstractLogEnabled
90 implements SiteTool
91 {
92
93
94
95
96
97
98
99 @Requirement
100 private ArtifactResolver artifactResolver;
101
102
103
104
105 @Requirement
106 private ArtifactFactory artifactFactory;
107
108
109
110
111 @Requirement
112 protected I18N i18n;
113
114
115
116
117 @Requirement
118 protected DecorationModelInheritanceAssembler assembler;
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 populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true );
484
485 if ( decorationModel.getBannerLeft() == null )
486 {
487
488 Banner banner = new Banner();
489 banner.setName( project.getName() );
490 decorationModel.setBannerLeft( banner );
491 }
492
493 return decorationModel;
494 }
495
496
497 public String getInterpolatedSiteDescriptorContent( Map<String, String> props, MavenProject aProject,
498 String siteDescriptorContent )
499 throws SiteToolException
500 {
501 checkNotNull( "props", props );
502
503
504 return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false );
505 }
506
507 private String getInterpolatedSiteDescriptorContent( MavenProject aProject,
508 String siteDescriptorContent, boolean isEarly )
509 throws SiteToolException
510 {
511 checkNotNull( "aProject", aProject );
512 checkNotNull( "siteDescriptorContent", siteDescriptorContent );
513
514 RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
515
516 if ( isEarly )
517 {
518 interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) );
519 interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) );
520 }
521 else
522 {
523 interpolator.addValueSource( new ObjectBasedValueSource( aProject ) );
524 interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) );
525
526 try
527 {
528 interpolator.addValueSource( new EnvarBasedValueSource() );
529 }
530 catch ( IOException e )
531 {
532
533 throw new SiteToolException( "IOException: cannot interpolate environment properties: "
534 + e.getMessage(), e );
535 }
536 }
537
538 try
539 {
540
541 return interpolator.interpolate( siteDescriptorContent, isEarly ? null : "project" );
542 }
543 catch ( InterpolationException e )
544 {
545 throw new SiteToolException( "Cannot interpolate site descriptor: " + e.getMessage(), e );
546 }
547 }
548
549
550 public MavenProject getParentProject( MavenProject aProject, List<MavenProject> reactorProjects,
551 ArtifactRepository localRepository )
552 {
553 checkNotNull( "aProject", aProject );
554 checkNotNull( "reactorProjects", reactorProjects );
555 checkNotNull( "localRepository", localRepository );
556
557 if ( isMaven3OrMore() )
558 {
559
560 return aProject.getParent();
561 }
562
563 MavenProject parentProject = null;
564
565 MavenProject origParent = aProject.getParent();
566 if ( origParent != null )
567 {
568 for ( MavenProject reactorProject : reactorProjects )
569 {
570 if ( reactorProject.getGroupId().equals( origParent.getGroupId() )
571 && reactorProject.getArtifactId().equals( origParent.getArtifactId() )
572 && reactorProject.getVersion().equals( origParent.getVersion() ) )
573 {
574 parentProject = reactorProject;
575
576 getLogger().debug( "Parent project " + origParent.getId() + " picked from reactor" );
577 break;
578 }
579 }
580
581 if ( parentProject == null && aProject.getBasedir() != null
582 && StringUtils.isNotEmpty( aProject.getModel().getParent().getRelativePath() ) )
583 {
584 try
585 {
586 String relativePath = aProject.getModel().getParent().getRelativePath();
587
588 File pomFile = new File( aProject.getBasedir(), relativePath );
589
590 if ( pomFile.isDirectory() )
591 {
592 pomFile = new File( pomFile, "pom.xml" );
593 }
594 pomFile = new File( getNormalizedPath( pomFile.getPath() ) );
595
596 if ( pomFile.isFile() )
597 {
598 MavenProject mavenProject = mavenProjectBuilder.build( pomFile, localRepository, null );
599
600 if ( mavenProject.getGroupId().equals( origParent.getGroupId() )
601 && mavenProject.getArtifactId().equals( origParent.getArtifactId() )
602 && mavenProject.getVersion().equals( origParent.getVersion() ) )
603 {
604 parentProject = mavenProject;
605
606 getLogger().debug( "Parent project " + origParent.getId() + " loaded from a relative path: "
607 + relativePath );
608 }
609 }
610 }
611 catch ( ProjectBuildingException e )
612 {
613 getLogger().info( "Unable to load parent project " + origParent.getId() + " from a relative path: "
614 + e.getMessage() );
615 }
616 }
617
618 if ( parentProject == null )
619 {
620 try
621 {
622 parentProject = mavenProjectBuilder.buildFromRepository( aProject.getParentArtifact(), aProject
623 .getRemoteArtifactRepositories(), localRepository );
624
625 getLogger().debug( "Parent project " + origParent.getId() + " loaded from repository" );
626 }
627 catch ( ProjectBuildingException e )
628 {
629 getLogger().warn( "Unable to load parent project " + origParent.getId() + " from repository: "
630 + e.getMessage() );
631 }
632 }
633
634 if ( parentProject == null )
635 {
636
637
638 parentProject = origParent;
639
640 getLogger().debug( "Parent project " + origParent.getId() + " picked from original value" );
641 }
642 }
643 return parentProject;
644 }
645
646
647
648
649
650
651
652
653
654
655
656 private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project,
657 MavenProject parentProject, boolean keepInheritedRefs )
658 {
659 checkNotNull( "decorationModel", decorationModel );
660 checkNotNull( "project", project );
661 checkNotNull( "parentProject", parentProject );
662
663 Menu menu = decorationModel.getMenuRef( "parent" );
664
665 if ( menu == null )
666 {
667 return;
668 }
669
670 if ( keepInheritedRefs && menu.isInheritAsRef() )
671 {
672 return;
673 }
674
675 final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale;
676
677 String parentUrl = getDistMgmntSiteUrl( parentProject );
678
679 if ( parentUrl != null )
680 {
681 if ( parentUrl.endsWith( "/" ) )
682 {
683 parentUrl += "index.html";
684 }
685 else
686 {
687 parentUrl += "/index.html";
688 }
689
690 parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) );
691 }
692 else
693 {
694
695 File parentBasedir = parentProject.getBasedir();
696
697 if ( parentBasedir != null )
698 {
699
700 String parentPath = parentBasedir.getAbsolutePath();
701 String projectPath = project.getBasedir().getAbsolutePath();
702 parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html";
703 }
704 }
705
706
707 if ( parentUrl == null )
708 {
709 getLogger().warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." );
710 }
711 else
712 {
713 if ( menu.getName() == null )
714 {
715 menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) );
716 }
717
718 MenuItem item = new MenuItem();
719 item.setName( parentProject.getName() );
720 item.setHref( parentUrl );
721 menu.addItem( item );
722 }
723 }
724
725
726
727
728
729
730
731
732
733
734
735
736
737 private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project,
738 List<MavenProject> reactorProjects, ArtifactRepository localRepository,
739 boolean keepInheritedRefs )
740 throws SiteToolException
741 {
742 checkNotNull( "project", project );
743 checkNotNull( "reactorProjects", reactorProjects );
744 checkNotNull( "localRepository", localRepository );
745 checkNotNull( "decorationModel", decorationModel );
746
747 Menu menu = decorationModel.getMenuRef( "modules" );
748
749 if ( menu == null )
750 {
751 return;
752 }
753
754 if ( keepInheritedRefs && menu.isInheritAsRef() )
755 {
756 return;
757 }
758
759 final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ;
760
761
762 if ( project.getModules().size() > 0 )
763 {
764 if ( menu.getName() == null )
765 {
766 menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) );
767 }
768
769 getLogger().debug( "Attempting to load module information from local filesystem" );
770
771
772 List<Model> models = new ArrayList<Model>( project.getModules().size() );
773 for ( String module : (List<String>) project.getModules() )
774 {
775 Model model;
776 File f = new File( project.getBasedir(), module + "/pom.xml" );
777 if ( f.exists() )
778 {
779 try
780 {
781 model = mavenProjectBuilder.build( f, localRepository, null ).getModel();
782 }
783 catch ( ProjectBuildingException e )
784 {
785 throw new SiteToolException( "Unable to read local module-POM", e );
786 }
787 }
788 else
789 {
790 getLogger().warn( "No filesystem module-POM available" );
791
792 model = new Model();
793 model.setName( module );
794 setDistMgmntSiteUrl( model, module );
795 }
796 models.add( model );
797 }
798 populateModulesMenuItemsFromModels( project, models, menu );
799 }
800 else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null )
801 {
802
803 decorationModel.removeMenuRef( "modules" );
804 }
805 }
806
807
808 public void populateReportsMenu( DecorationModel decorationModel, Locale locale,
809 Map<String, List<MavenReport>> categories )
810 {
811 checkNotNull( "decorationModel", decorationModel );
812 checkNotNull( "categories", categories );
813
814 Menu menu = decorationModel.getMenuRef( "reports" );
815
816 if ( menu == null )
817 {
818 return;
819 }
820
821 final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale;
822
823 if ( menu.getName() == null )
824 {
825 menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) );
826 }
827
828 boolean found = false;
829 if ( menu.getItems().isEmpty() )
830 {
831 List<MavenReport> categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION );
832 if ( !isEmptyList( categoryReports ) )
833 {
834 MenuItem item = createCategoryMenu(
835 i18n.getString( "site-tool", llocale,
836 "decorationModel.menu.projectinformation" ),
837 "/project-info.html", categoryReports, llocale );
838 menu.getItems().add( item );
839 found = true;
840 }
841
842 categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS );
843 if ( !isEmptyList( categoryReports ) )
844 {
845 MenuItem item =
846 createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ),
847 "/project-reports.html", categoryReports, llocale );
848 menu.getItems().add( item );
849 found = true;
850 }
851 }
852 if ( !found )
853 {
854 decorationModel.removeMenuRef( "reports" );
855 }
856 }
857
858
859 public List<Locale> getSiteLocales( String locales )
860 {
861 if ( locales == null )
862 {
863 return Collections.singletonList( DEFAULT_LOCALE );
864 }
865
866 String[] localesArray = StringUtils.split( locales, "," );
867 List<Locale> localesList = new ArrayList<Locale>( localesArray.length );
868
869 for ( String localeString : localesArray )
870 {
871 Locale locale = codeToLocale( localeString );
872
873 if ( locale == null )
874 {
875 continue;
876 }
877
878 if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) )
879 {
880 if ( getLogger().isWarnEnabled() )
881 {
882 getLogger().warn( "The locale defined by '" + locale
883 + "' is not available in this Java Virtual Machine ("
884 + System.getProperty( "java.version" )
885 + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" );
886 }
887 continue;
888 }
889
890
891 if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) )
892 && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage()
893 .equals( locale.getLanguage() ) ) )
894 {
895 if ( getLogger().isWarnEnabled() )
896 {
897 getLogger().warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH )
898 + ") is not currently supported by Maven Site - IGNORING."
899 + "\nContributions are welcome and greatly appreciated!"
900 + "\nIf you want to contribute a new translation, please visit "
901 + "http://maven.apache.org/plugins/localization.html for detailed instructions." );
902 }
903
904 continue;
905 }
906
907 localesList.add( locale );
908 }
909
910 if ( localesList.isEmpty() )
911 {
912 localesList = Collections.singletonList( DEFAULT_LOCALE );
913 }
914
915 return localesList;
916 }
917
918
919
920
921
922
923
924
925
926
927
928 private Locale codeToLocale( String localeCode )
929 {
930 if ( localeCode == null )
931 {
932 return null;
933 }
934
935 if ( "default".equalsIgnoreCase( localeCode ) )
936 {
937 return Locale.getDefault();
938 }
939
940 String language = "";
941 String country = "";
942 String variant = "";
943
944 StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" );
945 final int maxTokens = 3;
946 if ( tokenizer.countTokens() > maxTokens )
947 {
948 if ( getLogger().isWarnEnabled() )
949 {
950 getLogger().warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" );
951 }
952 return null;
953 }
954
955 if ( tokenizer.hasMoreTokens() )
956 {
957 language = tokenizer.nextToken();
958 if ( tokenizer.hasMoreTokens() )
959 {
960 country = tokenizer.nextToken();
961 if ( tokenizer.hasMoreTokens() )
962 {
963 variant = tokenizer.nextToken();
964 }
965 }
966 }
967
968 return new Locale( language, country, variant );
969 }
970
971
972
973
974
975
976
977
978
979
980 protected static String getNormalizedPath( String path )
981 {
982 String normalized = FilenameUtils.normalize( path );
983 if ( normalized == null )
984 {
985 normalized = path;
986 }
987 return ( normalized == null ) ? null : normalized.replace( '\\', '/' );
988 }
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004 private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository,
1005 List<ArtifactRepository> repositories, Locale locale )
1006 throws IOException, ArtifactResolutionException, ArtifactNotFoundException
1007 {
1008 File result;
1009
1010
1011 Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(),
1012 project.getArtifactId(),
1013 project.getVersion(), "xml",
1014 "site_" + locale.getLanguage() );
1015
1016 boolean found = false;
1017 try
1018 {
1019 artifactResolver.resolve( artifact, repositories, localRepository );
1020
1021 result = artifact.getFile();
1022
1023
1024 if ( result.length() > 0 )
1025 {
1026 found = true;
1027 }
1028 else
1029 {
1030 getLogger().debug( "No site descriptor found for " + project.getId() + " for locale "
1031 + locale.getLanguage() );
1032 }
1033 }
1034 catch ( ArtifactNotFoundException e )
1035 {
1036 getLogger().debug( "Unable to locate site descriptor for locale " + locale.getLanguage() + ": " + e );
1037
1038
1039
1040 result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) );
1041 result.getParentFile().mkdirs();
1042 result.createNewFile();
1043 }
1044
1045 if ( !found )
1046 {
1047 artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(),
1048 project.getVersion(), "xml", "site" );
1049 try
1050 {
1051 artifactResolver.resolve( artifact, repositories, localRepository );
1052 }
1053 catch ( ArtifactNotFoundException e )
1054 {
1055
1056 result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) );
1057 result.getParentFile().mkdirs();
1058 result.createNewFile();
1059
1060 throw e;
1061 }
1062
1063 result = artifact.getFile();
1064
1065
1066 if ( result.length() == 0 )
1067 {
1068 getLogger().debug( "No site descriptor found for " + project.getId() + " without locale" );
1069 result = null;
1070 }
1071 }
1072
1073 return result;
1074 }
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088 private Map.Entry<DecorationModel, MavenProject> getDecorationModel( int depth, File siteDirectory, Locale locale,
1089 MavenProject project,
1090 List<MavenProject> reactorProjects,
1091 ArtifactRepository localRepository,
1092 List<ArtifactRepository> repositories )
1093 throws SiteToolException
1094 {
1095
1096 File siteDescriptor;
1097 if ( project.getBasedir() == null )
1098 {
1099
1100 try
1101 {
1102 siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale );
1103 }
1104 catch ( SiteToolException e )
1105 {
1106 throw new SiteToolException( "The site descriptor cannot be resolved from the repository: "
1107 + e.getMessage(), e );
1108 }
1109 }
1110 else
1111 {
1112
1113 siteDescriptor = getSiteDescriptor( siteDirectory, locale );
1114 }
1115
1116
1117 DecorationModel decoration = null;
1118 Reader siteDescriptorReader = null;
1119 try
1120 {
1121 if ( siteDescriptor != null && siteDescriptor.exists() )
1122 {
1123 getLogger().debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) )
1124 + " site descriptor from " + siteDescriptor );
1125
1126 siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor );
1127
1128 String siteDescriptorContent = readSiteDescriptor( siteDescriptorReader, project.getId() );
1129
1130
1131 siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true );
1132
1133 decoration = readDecorationModel( siteDescriptorContent );
1134 decoration.setLastModified( siteDescriptor.lastModified() );
1135 }
1136 else
1137 {
1138 getLogger().debug( "No site descriptor found for " + project.getId() );
1139 }
1140 }
1141 catch ( IOException e )
1142 {
1143 throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from "
1144 + siteDescriptor, e );
1145 }
1146 finally
1147 {
1148 IOUtil.close( siteDescriptorReader );
1149 }
1150
1151
1152 MavenProject parentProject = getParentProject( project, reactorProjects, localRepository );
1153
1154
1155 if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) )
1156 {
1157 depth++;
1158 getLogger().debug( "Looking for site descriptor of level " + depth + " parent project: "
1159 + parentProject.getId() );
1160
1161 File parentSiteDirectory = null;
1162 if ( parentProject.getBasedir() != null )
1163 {
1164
1165 String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(),
1166 siteDescriptor.getParentFile().getAbsolutePath() );
1167
1168 parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath );
1169
1170
1171 }
1172
1173 DecorationModel parentDecoration =
1174 getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository,
1175 repositories ).getKey();
1176
1177
1178
1179 if ( decoration == null && parentDecoration != null )
1180 {
1181
1182 decoration = new DecorationModel();
1183 }
1184
1185 String name = project.getName();
1186 if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) )
1187 {
1188 name = decoration.getName();
1189 }
1190
1191
1192 String projectDistMgmnt = getDistMgmntSiteUrl( project );
1193 String parentDistMgmnt = getDistMgmntSiteUrl( parentProject );
1194 if ( getLogger().isDebugEnabled() )
1195 {
1196 getLogger().debug( "Site decoration model inheritance: assembling child with level " + depth
1197 + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = "
1198 + parentDistMgmnt );
1199 }
1200 assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt,
1201 parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt );
1202 }
1203
1204 return new AbstractMap.SimpleEntry<DecorationModel, MavenProject>( decoration, parentProject );
1205 }
1206
1207
1208
1209
1210
1211
1212 private DecorationModel readDecorationModel( String siteDescriptorContent )
1213 throws SiteToolException
1214 {
1215 try
1216 {
1217 return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) );
1218 }
1219 catch ( XmlPullParserException e )
1220 {
1221 throw new SiteToolException( "Error parsing site descriptor", e );
1222 }
1223 catch ( IOException e )
1224 {
1225 throw new SiteToolException( "Error reading site descriptor", e );
1226 }
1227 }
1228
1229 private String decorationModelToString( DecorationModel decoration )
1230 throws SiteToolException
1231 {
1232 StringWriter writer = new StringWriter();
1233
1234 try
1235 {
1236 new DecorationXpp3Writer().write( writer, decoration );
1237 return writer.toString();
1238 }
1239 catch ( IOException e )
1240 {
1241 throw new SiteToolException( "Error reading site descriptor", e );
1242 }
1243 finally
1244 {
1245 IOUtil.close( writer );
1246 }
1247 }
1248
1249 private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar )
1250 {
1251
1252 StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) );
1253 StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) );
1254
1255 int count = 0;
1256
1257
1258 while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() )
1259 {
1260 if ( separatorChar == '\\' )
1261 {
1262 if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) )
1263 {
1264 break;
1265 }
1266 }
1267 else
1268 {
1269 if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) )
1270 {
1271 break;
1272 }
1273 }
1274
1275 count++;
1276 }
1277
1278
1279
1280
1281 toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) );
1282 fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) );
1283
1284 while ( count-- > 0 )
1285 {
1286 fromTokeniser.nextToken();
1287 toTokeniser.nextToken();
1288 }
1289
1290 StringBuilder relativePath = new StringBuilder();
1291
1292
1293 while ( fromTokeniser.hasMoreTokens() )
1294 {
1295 fromTokeniser.nextToken();
1296
1297 relativePath.append( ".." );
1298
1299 if ( fromTokeniser.hasMoreTokens() )
1300 {
1301 relativePath.append( separatorChar );
1302 }
1303 }
1304
1305 if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() )
1306 {
1307 relativePath.append( separatorChar );
1308 }
1309
1310
1311 while ( toTokeniser.hasMoreTokens() )
1312 {
1313 relativePath.append( toTokeniser.nextToken() );
1314
1315 if ( toTokeniser.hasMoreTokens() )
1316 {
1317 relativePath.append( separatorChar );
1318 }
1319 }
1320 return relativePath.toString();
1321 }
1322
1323
1324
1325
1326
1327
1328 private void populateModulesMenuItemsFromModels( MavenProject project, List<Model> models, Menu menu )
1329 {
1330 for ( Model model : models )
1331 {
1332 String reactorUrl = getDistMgmntSiteUrl( model );
1333 String name = ( model.getName() == null ) ? model.getArtifactId() : model.getName();
1334
1335 appendMenuItem( project, menu, name, reactorUrl, model.getArtifactId() );
1336 }
1337 }
1338
1339
1340
1341
1342
1343
1344
1345
1346 private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref )
1347 {
1348 String selectedHref = href;
1349
1350 if ( selectedHref == null )
1351 {
1352 selectedHref = defaultHref;
1353 }
1354
1355 MenuItem item = new MenuItem();
1356 item.setName( name );
1357
1358 String baseUrl = getDistMgmntSiteUrl( project );
1359 if ( baseUrl != null )
1360 {
1361 selectedHref = getRelativePath( selectedHref, baseUrl );
1362 }
1363
1364 if ( selectedHref.endsWith( "/" ) )
1365 {
1366 item.setHref( selectedHref + "index.html" );
1367 }
1368 else
1369 {
1370 item.setHref( selectedHref + "/index.html" );
1371 }
1372 menu.addItem( item );
1373 }
1374
1375
1376
1377
1378
1379
1380
1381
1382 private MenuItem createCategoryMenu( String name, String href, List<MavenReport> categoryReports, Locale locale )
1383 {
1384 MenuItem item = new MenuItem();
1385 item.setName( name );
1386 item.setCollapse( true );
1387 item.setHref( href );
1388
1389
1390
1391
1392 for ( MavenReport report : categoryReports )
1393 {
1394 MenuItem subitem = new MenuItem();
1395 subitem.setName( report.getName( locale ) );
1396 subitem.setHref( report.getOutputName() + ".html" );
1397 item.getItems().add( subitem );
1398 }
1399
1400 return item;
1401 }
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413 private static boolean isEmptyList( List<?> list )
1414 {
1415 return list == null || list.isEmpty();
1416 }
1417
1418
1419
1420
1421
1422
1423
1424 private static String getDistMgmntSiteUrl( MavenProject project )
1425 {
1426 return getDistMgmntSiteUrl( project.getDistributionManagement() );
1427 }
1428
1429
1430
1431
1432
1433
1434
1435 private static String getDistMgmntSiteUrl( Model model )
1436 {
1437 return getDistMgmntSiteUrl( model.getDistributionManagement() );
1438 }
1439
1440 private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt )
1441 {
1442 if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null )
1443 {
1444 return urlEncode( distMgmnt.getSite().getUrl() );
1445 }
1446
1447 return null;
1448 }
1449
1450 private static String urlEncode( final String url )
1451 {
1452 if ( url == null )
1453 {
1454 return null;
1455 }
1456
1457 try
1458 {
1459 return new File( url ).toURI().toURL().toExternalForm();
1460 }
1461 catch ( MalformedURLException ex )
1462 {
1463 return url;
1464 }
1465 }
1466
1467 private static void setDistMgmntSiteUrl( Model model, String url )
1468 {
1469 if ( model.getDistributionManagement() == null )
1470 {
1471 model.setDistributionManagement( new DistributionManagement() );
1472 }
1473
1474 if ( model.getDistributionManagement().getSite() == null )
1475 {
1476 model.getDistributionManagement().setSite( new Site() );
1477 }
1478
1479 model.getDistributionManagement().getSite().setUrl( url );
1480 }
1481
1482 private void checkNotNull( String name, Object value )
1483 {
1484 if ( value == null )
1485 {
1486 throw new IllegalArgumentException( "The parameter '" + name + "' cannot be null." );
1487 }
1488 }
1489
1490
1491
1492
1493 private static boolean isMaven3OrMore()
1494 {
1495 return new DefaultArtifactVersion( getMavenVersion() ).getMajorVersion() >= 3;
1496 }
1497
1498 private static String getMavenVersion()
1499 {
1500
1501
1502
1503
1504 final Properties properties = new Properties();
1505 final String corePomProperties = "META-INF/maven/org.apache.maven/maven-core/pom.properties";
1506 final InputStream in = MavenProject.class.getClassLoader().getResourceAsStream( corePomProperties );
1507 try
1508 {
1509 properties.load( in );
1510 }
1511 catch ( IOException ioe )
1512 {
1513 return "";
1514 }
1515 finally
1516 {
1517 IOUtil.close( in );
1518 }
1519
1520 return properties.getProperty( "version" ).trim();
1521 }
1522 }