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