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