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