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 javax.inject.Inject;
23 import javax.inject.Named;
24
25 import java.io.File;
26 import java.io.IOException;
27 import java.io.Reader;
28 import java.io.StringReader;
29 import java.io.StringWriter;
30 import java.net.MalformedURLException;
31 import java.net.URL;
32 import java.util.AbstractMap;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collections;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Map;
39 import java.util.Objects;
40 import java.util.StringTokenizer;
41
42 import org.apache.commons.io.FilenameUtils;
43 import org.apache.maven.artifact.Artifact;
44 import org.apache.maven.artifact.factory.ArtifactFactory;
45 import org.apache.maven.artifact.repository.ArtifactRepository;
46 import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
47 import org.apache.maven.artifact.resolver.ArtifactResolutionException;
48 import org.apache.maven.artifact.resolver.ArtifactResolver;
49 import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
50 import org.apache.maven.artifact.versioning.VersionRange;
51 import org.apache.maven.doxia.site.decoration.Banner;
52 import org.apache.maven.doxia.site.decoration.DecorationModel;
53 import org.apache.maven.doxia.site.decoration.Menu;
54 import org.apache.maven.doxia.site.decoration.MenuItem;
55 import org.apache.maven.doxia.site.decoration.Skin;
56 import org.apache.maven.doxia.site.decoration.inheritance.DecorationModelInheritanceAssembler;
57 import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Reader;
58 import org.apache.maven.doxia.site.decoration.io.xpp3.DecorationXpp3Writer;
59 import org.apache.maven.model.DistributionManagement;
60 import org.apache.maven.model.Site;
61 import org.apache.maven.project.DefaultProjectBuildingRequest;
62 import org.apache.maven.project.MavenProject;
63 import org.apache.maven.project.ProjectBuilder;
64 import org.apache.maven.project.ProjectBuildingException;
65 import org.apache.maven.project.ProjectBuildingRequest;
66 import org.apache.maven.project.ProjectBuildingResult;
67 import org.apache.maven.reporting.MavenReport;
68 import org.codehaus.plexus.i18n.I18N;
69 import org.codehaus.plexus.util.IOUtil;
70 import org.codehaus.plexus.util.ReaderFactory;
71 import org.codehaus.plexus.util.StringUtils;
72 import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
73 import org.codehaus.plexus.interpolation.InterpolationException;
74 import org.codehaus.plexus.interpolation.MapBasedValueSource;
75 import org.codehaus.plexus.interpolation.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 import org.slf4j.Logger;
80 import org.slf4j.LoggerFactory;
81
82
83
84
85
86
87 @Named
88 public class DefaultSiteTool
89 implements SiteTool
90 {
91 private static final Logger LOGGER = LoggerFactory.getLogger( DefaultSiteTool.class );
92
93
94
95
96
97
98
99
100 @Inject
101 private ArtifactResolver artifactResolver;
102
103
104
105
106 @Inject
107 private ArtifactFactory artifactFactory;
108
109
110
111
112 @Inject
113 protected I18N i18n;
114
115
116
117
118 @Inject
119 protected DecorationModelInheritanceAssembler assembler;
120
121
122
123
124 @Inject
125 protected ProjectBuilder projectBuilder;
126
127
128
129
130
131 public Artifact getSkinArtifactFromRepository( ArtifactRepository localRepository,
132 List<ArtifactRepository> remoteArtifactRepositories,
133 DecorationModel decoration )
134 throws SiteToolException
135 {
136 Objects.requireNonNull( localRepository, "localRepository cannot be null" );
137 Objects.requireNonNull( remoteArtifactRepositories, "remoteArtifactRepositories cannot be null" );
138 Objects.requireNonNull( decoration, "decoration cannot be null" );
139
140 Skin skin = decoration.getSkin();
141
142 if ( skin == null )
143 {
144 skin = Skin.getDefaultSkin();
145 }
146
147 String version = skin.getVersion();
148 Artifact artifact;
149 try
150 {
151 if ( version == null )
152 {
153 version = Artifact.RELEASE_VERSION;
154 }
155 VersionRange versionSpec = VersionRange.createFromVersionSpec( version );
156 artifact = artifactFactory.createDependencyArtifact( skin.getGroupId(), skin.getArtifactId(), versionSpec,
157 "jar", null, null );
158
159 artifactResolver.resolve( artifact, remoteArtifactRepositories, localRepository );
160 }
161 catch ( InvalidVersionSpecificationException e )
162 {
163 throw new SiteToolException( "The skin version '" + version + "' is not valid", e );
164 }
165 catch ( ArtifactResolutionException e )
166 {
167 throw new SiteToolException( "Unable to find skin", e );
168 }
169 catch ( ArtifactNotFoundException e )
170 {
171 throw new SiteToolException( "The skin does not exist", 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 Objects.requireNonNull( to, "to cannot be null" );
193 Objects.requireNonNull( from, "from cannot be null" );
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 );
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 );
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 Objects.requireNonNull( siteDirectory, "siteDirectory cannot be null" );
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 Objects.requireNonNull( project, "project cannot be null" );
378 Objects.requireNonNull( localRepository, "localRepository cannot be null" );
379 Objects.requireNonNull( repositories, "repositories cannot be null" );
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( "Unable to locate site descriptor", e );
390 return null;
391 }
392 catch ( ArtifactResolutionException e )
393 {
394 throw new SiteToolException( "Unable to locate site descriptor", e );
395 }
396 catch ( IOException e )
397 {
398 throw new SiteToolException( "Unable to locate site descriptor", e );
399 }
400 }
401
402
403 public DecorationModel getDecorationModel( File siteDirectory, Locale locale, MavenProject project,
404 List<MavenProject> reactorProjects, ArtifactRepository localRepository,
405 List<ArtifactRepository> repositories )
406 throws SiteToolException
407 {
408 Objects.requireNonNull( project, "project cannot be null" );
409 Objects.requireNonNull( reactorProjects, "reactorProjects cannot be null" );
410 Objects.requireNonNull( localRepository, "localRepository cannot be null" );
411 Objects.requireNonNull( repositories, "repositories cannot be null" );
412
413 final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale;
414
415 LOGGER.debug( "Computing decoration model of " + project.getId() + " for locale " + llocale );
416
417 Map.Entry<DecorationModel, MavenProject> result =
418 getDecorationModel( 0, siteDirectory, llocale, project, reactorProjects, localRepository, repositories );
419 DecorationModel decorationModel = result.getKey();
420 MavenProject parentProject = result.getValue();
421
422 if ( decorationModel == null )
423 {
424 LOGGER.debug( "Using default site descriptor" );
425
426 String siteDescriptorContent;
427
428 Reader reader = null;
429 try
430 {
431
432 reader = ReaderFactory.newXmlReader( getClass().getResourceAsStream( "/default-site.xml" ) );
433 siteDescriptorContent = IOUtil.toString( reader );
434 }
435 catch ( IOException e )
436 {
437 throw new SiteToolException( "Error reading default site descriptor", e );
438 }
439 finally
440 {
441 IOUtil.close( reader );
442 }
443
444 decorationModel = readDecorationModel( siteDescriptorContent );
445 }
446
447
448 String siteDescriptorContent = decorationModelToString( decorationModel );
449
450
451 siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, false );
452
453 decorationModel = readDecorationModel( siteDescriptorContent );
454
455 if ( parentProject != null )
456 {
457 populateParentMenu( decorationModel, llocale, project, parentProject, true );
458 }
459
460 try
461 {
462 populateModulesMenu( decorationModel, llocale, project, reactorProjects, localRepository, true );
463 }
464 catch ( IOException e )
465 {
466 throw new SiteToolException( "Error while populating modules menu", e );
467 }
468
469 if ( decorationModel.getBannerLeft() == null )
470 {
471
472 Banner banner = new Banner();
473 banner.setName( project.getName() );
474 decorationModel.setBannerLeft( banner );
475 }
476
477 return decorationModel;
478 }
479
480
481 public String getInterpolatedSiteDescriptorContent( Map<String, String> props, MavenProject aProject,
482 String siteDescriptorContent )
483 throws SiteToolException
484 {
485 Objects.requireNonNull( props, "props cannot be null" );
486
487
488 return getInterpolatedSiteDescriptorContent( aProject, siteDescriptorContent, false );
489 }
490
491 private String getInterpolatedSiteDescriptorContent( MavenProject aProject,
492 String siteDescriptorContent, boolean isEarly )
493 throws SiteToolException
494 {
495 Objects.requireNonNull( aProject, "aProject cannot be null" );
496 Objects.requireNonNull( siteDescriptorContent, "siteDescriptorContent cannot be null" );
497
498 RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
499
500 if ( isEarly )
501 {
502 interpolator.addValueSource( new PrefixedObjectValueSource( "this.", aProject ) );
503 interpolator.addValueSource( new PrefixedPropertiesValueSource( "this.", aProject.getProperties() ) );
504 }
505 else
506 {
507 interpolator.addValueSource( new PrefixedObjectValueSource( "project.", aProject ) );
508 interpolator.addValueSource( new MapBasedValueSource( aProject.getProperties() ) );
509
510 try
511 {
512 interpolator.addValueSource( new EnvarBasedValueSource() );
513 }
514 catch ( IOException e )
515 {
516
517 throw new SiteToolException( "Cannot interpolate environment properties", e );
518 }
519 }
520
521 try
522 {
523
524 return interpolator.interpolate( siteDescriptorContent );
525 }
526 catch ( InterpolationException e )
527 {
528 throw new SiteToolException( "Cannot interpolate site descriptor", e );
529 }
530 }
531
532
533 public MavenProject getParentProject( MavenProject aProject, List<MavenProject> reactorProjects,
534 ArtifactRepository localRepository )
535 {
536 Objects.requireNonNull( aProject, "aProject cannot be null" );
537 Objects.requireNonNull( reactorProjects, "reactorProjects cannot be null" );
538 Objects.requireNonNull( localRepository, "localRepository cannot be null" );
539
540 return aProject.getParent();
541 }
542
543
544
545
546
547
548
549
550
551
552
553 private void populateParentMenu( DecorationModel decorationModel, Locale locale, MavenProject project,
554 MavenProject parentProject, boolean keepInheritedRefs )
555 {
556 Objects.requireNonNull( decorationModel, "decorationModel cannot be null" );
557 Objects.requireNonNull( project, "project cannot be null" );
558 Objects.requireNonNull( parentProject, "parentProject cannot be null" );
559
560 Menu menu = decorationModel.getMenuRef( "parent" );
561
562 if ( menu == null )
563 {
564 return;
565 }
566
567 if ( keepInheritedRefs && menu.isInheritAsRef() )
568 {
569 return;
570 }
571
572 final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale;
573
574 String parentUrl = getDistMgmntSiteUrl( parentProject );
575
576 if ( parentUrl != null )
577 {
578 if ( parentUrl.endsWith( "/" ) )
579 {
580 parentUrl += "index.html";
581 }
582 else
583 {
584 parentUrl += "/index.html";
585 }
586
587 parentUrl = getRelativePath( parentUrl, getDistMgmntSiteUrl( project ) );
588 }
589 else
590 {
591
592 File parentBasedir = parentProject.getBasedir();
593
594 if ( parentBasedir != null )
595 {
596
597 String parentPath = parentBasedir.getAbsolutePath();
598 String projectPath = project.getBasedir().getAbsolutePath();
599 parentUrl = getRelativePath( parentPath, projectPath ) + "/index.html";
600 }
601 }
602
603
604 if ( parentUrl == null )
605 {
606 LOGGER.warn( "Unable to find a URL to the parent project. The parent menu will NOT be added." );
607 }
608 else
609 {
610 if ( menu.getName() == null )
611 {
612 menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.parentproject" ) );
613 }
614
615 MenuItem item = new MenuItem();
616 item.setName( parentProject.getName() );
617 item.setHref( parentUrl );
618 menu.addItem( item );
619 }
620 }
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635 private void populateModulesMenu( DecorationModel decorationModel, Locale locale, MavenProject project,
636 List<MavenProject> reactorProjects, ArtifactRepository localRepository,
637 boolean keepInheritedRefs )
638 throws SiteToolException, IOException
639 {
640 Objects.requireNonNull( project, "project cannot be null" );
641 Objects.requireNonNull( reactorProjects, "reactorProjects cannot be null" );
642 Objects.requireNonNull( localRepository, "localRepository cannot be null" );
643 Objects.requireNonNull( decorationModel, "decorationModel cannot be null" );
644
645 Menu menu = decorationModel.getMenuRef( "modules" );
646
647 if ( menu == null )
648 {
649 return;
650 }
651
652 if ( keepInheritedRefs && menu.isInheritAsRef() )
653 {
654 return;
655 }
656
657 final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale ;
658
659
660 if ( project.getModules().size() > 0 )
661 {
662 if ( menu.getName() == null )
663 {
664 menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectmodules" ) );
665 }
666
667 for ( String module : (List<String>) project.getModules() )
668 {
669 MavenProject moduleProject = getModuleFromReactor( project, reactorProjects, module );
670
671 if ( moduleProject == null )
672 {
673 LOGGER.warn( "Module " + module
674 + " not found in reactor: loading locally" );
675
676 File f = new File( project.getBasedir(), module + "/pom.xml" );
677 if ( f.exists() )
678 {
679 try
680 {
681 ProjectBuildingRequest request = new DefaultProjectBuildingRequest();
682 request.setLocalRepository( localRepository );
683
684 ProjectBuildingResult result = projectBuilder.build( f, request );
685 moduleProject = result.getProject();
686 }
687 catch ( ProjectBuildingException e )
688 {
689 throw new SiteToolException( "Unable to read local module POM", e );
690 }
691 }
692 else
693 {
694 LOGGER.warn( "No filesystem module POM available" );
695
696 moduleProject = new MavenProject();
697 moduleProject.setName( module );
698 moduleProject.setDistributionManagement( new DistributionManagement() );
699 moduleProject.getDistributionManagement().setSite( new Site() );
700 moduleProject.getDistributionManagement().getSite().setUrl( module );
701 }
702 }
703
704 String siteUrl = getDistMgmntSiteUrl( moduleProject );
705 String itemName =
706 ( moduleProject.getName() == null ) ? moduleProject.getArtifactId() : moduleProject.getName();
707
708 appendMenuItem( project, menu, itemName, siteUrl, moduleProject.getArtifactId() );
709 }
710 }
711 else if ( decorationModel.getMenuRef( "modules" ).getInherit() == null )
712 {
713
714 decorationModel.removeMenuRef( "modules" );
715 }
716 }
717
718 private static MavenProject getModuleFromReactor( MavenProject project, List<MavenProject> reactorProjects,
719 String module )
720 throws IOException
721 {
722 File moduleBasedir = new File( project.getBasedir(), module ).getCanonicalFile();
723
724 for ( MavenProject reactorProject : reactorProjects )
725 {
726 if ( moduleBasedir.equals( reactorProject.getBasedir() ) )
727 {
728 return reactorProject;
729 }
730 }
731
732
733 return null;
734 }
735
736
737 public void populateReportsMenu( DecorationModel decorationModel, Locale locale,
738 Map<String, List<MavenReport>> categories )
739 {
740 Objects.requireNonNull( decorationModel, "decorationModel cannot be null" );
741 Objects.requireNonNull( categories, "categories cannot be null" );
742
743 Menu menu = decorationModel.getMenuRef( "reports" );
744
745 if ( menu == null )
746 {
747 return;
748 }
749
750 final Locale llocale = ( locale == null ) ? Locale.getDefault() : locale;
751
752 if ( menu.getName() == null )
753 {
754 menu.setName( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectdocumentation" ) );
755 }
756
757 boolean found = false;
758 if ( menu.getItems().isEmpty() )
759 {
760 List<MavenReport> categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_INFORMATION );
761 if ( !isEmptyList( categoryReports ) )
762 {
763 MenuItem item = createCategoryMenu(
764 i18n.getString( "site-tool", llocale,
765 "decorationModel.menu.projectinformation" ),
766 "/project-info.html", categoryReports, llocale );
767 menu.getItems().add( item );
768 found = true;
769 }
770
771 categoryReports = categories.get( MavenReport.CATEGORY_PROJECT_REPORTS );
772 if ( !isEmptyList( categoryReports ) )
773 {
774 MenuItem item =
775 createCategoryMenu( i18n.getString( "site-tool", llocale, "decorationModel.menu.projectreports" ),
776 "/project-reports.html", categoryReports, llocale );
777 menu.getItems().add( item );
778 found = true;
779 }
780 }
781 if ( !found )
782 {
783 decorationModel.removeMenuRef( "reports" );
784 }
785 }
786
787
788 public List<Locale> getSiteLocales( String locales )
789 {
790 if ( locales == null )
791 {
792 return Collections.singletonList( DEFAULT_LOCALE );
793 }
794
795 String[] localesArray = StringUtils.split( locales, "," );
796 List<Locale> localesList = new ArrayList<Locale>( localesArray.length );
797
798 for ( String localeString : localesArray )
799 {
800 Locale locale = codeToLocale( localeString );
801
802 if ( locale == null )
803 {
804 continue;
805 }
806
807 if ( !Arrays.asList( Locale.getAvailableLocales() ).contains( locale ) )
808 {
809 if ( LOGGER.isWarnEnabled() )
810 {
811 LOGGER.warn( "The locale defined by '" + locale
812 + "' is not available in this Java Virtual Machine ("
813 + System.getProperty( "java.version" )
814 + " from " + System.getProperty( "java.vendor" ) + ") - IGNORING" );
815 }
816 continue;
817 }
818
819
820 if ( ( !locale.getLanguage().equals( DEFAULT_LOCALE.getLanguage() ) )
821 && ( !i18n.getBundle( "site-tool", locale ).getLocale().getLanguage()
822 .equals( locale.getLanguage() ) ) )
823 {
824 if ( LOGGER.isWarnEnabled() )
825 {
826 LOGGER.warn( "The locale '" + locale + "' (" + locale.getDisplayName( Locale.ENGLISH )
827 + ") is not currently supported by Maven Site - IGNORING."
828 + "\nContributions are welcome and greatly appreciated!"
829 + "\nIf you want to contribute a new translation, please visit "
830 + "http://maven.apache.org/plugins/localization.html for detailed instructions." );
831 }
832
833 continue;
834 }
835
836 localesList.add( locale );
837 }
838
839 if ( localesList.isEmpty() )
840 {
841 localesList = Collections.singletonList( DEFAULT_LOCALE );
842 }
843
844 return localesList;
845 }
846
847
848
849
850
851
852
853
854
855
856
857 private Locale codeToLocale( String localeCode )
858 {
859 if ( localeCode == null )
860 {
861 return null;
862 }
863
864 if ( "default".equalsIgnoreCase( localeCode ) )
865 {
866 return Locale.getDefault();
867 }
868
869 String language = "";
870 String country = "";
871 String variant = "";
872
873 StringTokenizer tokenizer = new StringTokenizer( localeCode, "_" );
874 final int maxTokens = 3;
875 if ( tokenizer.countTokens() > maxTokens )
876 {
877 if ( LOGGER.isWarnEnabled() )
878 {
879 LOGGER.warn( "Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING" );
880 }
881 return null;
882 }
883
884 if ( tokenizer.hasMoreTokens() )
885 {
886 language = tokenizer.nextToken();
887 if ( tokenizer.hasMoreTokens() )
888 {
889 country = tokenizer.nextToken();
890 if ( tokenizer.hasMoreTokens() )
891 {
892 variant = tokenizer.nextToken();
893 }
894 }
895 }
896
897 return new Locale( language, country, variant );
898 }
899
900
901
902
903
904
905
906
907
908
909 protected static String getNormalizedPath( String path )
910 {
911 String normalized = FilenameUtils.normalize( path );
912 if ( normalized == null )
913 {
914 normalized = path;
915 }
916 return ( normalized == null ) ? null : normalized.replace( '\\', '/' );
917 }
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933 private File resolveSiteDescriptor( MavenProject project, ArtifactRepository localRepository,
934 List<ArtifactRepository> repositories, Locale locale )
935 throws IOException, ArtifactResolutionException, ArtifactNotFoundException
936 {
937 File result;
938
939
940 Artifact artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(),
941 project.getArtifactId(),
942 project.getVersion(), "xml",
943 "site_" + locale.getLanguage() );
944
945 boolean found = false;
946 try
947 {
948 artifactResolver.resolve( artifact, repositories, localRepository );
949
950 result = artifact.getFile();
951
952
953 if ( result.length() > 0 )
954 {
955 found = true;
956 }
957 else
958 {
959 LOGGER.debug( "No site descriptor found for " + project.getId() + " for locale "
960 + locale.getLanguage() + ", trying without locale..." );
961 }
962 }
963 catch ( ArtifactNotFoundException e )
964 {
965 LOGGER.debug( "Unable to locate site descriptor for locale " + locale.getLanguage(), e );
966
967
968
969 result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) );
970 result.getParentFile().mkdirs();
971 result.createNewFile();
972 }
973
974 if ( !found )
975 {
976 artifact = artifactFactory.createArtifactWithClassifier( project.getGroupId(), project.getArtifactId(),
977 project.getVersion(), "xml", "site" );
978 try
979 {
980 artifactResolver.resolve( artifact, repositories, localRepository );
981 }
982 catch ( ArtifactNotFoundException e )
983 {
984
985 result = new File( localRepository.getBasedir(), localRepository.pathOf( artifact ) );
986 result.getParentFile().mkdirs();
987 result.createNewFile();
988
989 throw e;
990 }
991
992 result = artifact.getFile();
993
994
995 if ( result.length() == 0 )
996 {
997 LOGGER.debug( "No site descriptor found for " + project.getId() + " without locale." );
998 result = null;
999 }
1000 }
1001
1002 return result;
1003 }
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016 private Map.Entry<DecorationModel, MavenProject> getDecorationModel( int depth, File siteDirectory, Locale locale,
1017 MavenProject project,
1018 List<MavenProject> reactorProjects,
1019 ArtifactRepository localRepository,
1020 List<ArtifactRepository> repositories )
1021 throws SiteToolException
1022 {
1023
1024 File siteDescriptor;
1025 if ( project.getBasedir() == null )
1026 {
1027
1028 try
1029 {
1030 siteDescriptor = getSiteDescriptorFromRepository( project, localRepository, repositories, locale );
1031 }
1032 catch ( SiteToolException e )
1033 {
1034 throw new SiteToolException( "The site descriptor cannot be resolved from the repository", e );
1035 }
1036 }
1037 else
1038 {
1039
1040 siteDescriptor = getSiteDescriptor( siteDirectory, locale );
1041 }
1042
1043
1044 DecorationModel decoration = null;
1045 Reader siteDescriptorReader = null;
1046 try
1047 {
1048 if ( siteDescriptor != null && siteDescriptor.exists() )
1049 {
1050 LOGGER.debug( "Reading" + ( depth == 0 ? "" : ( " parent level " + depth ) )
1051 + " site descriptor from " + siteDescriptor );
1052
1053 siteDescriptorReader = ReaderFactory.newXmlReader( siteDescriptor );
1054
1055 String siteDescriptorContent = IOUtil.toString( siteDescriptorReader );
1056
1057
1058 siteDescriptorContent = getInterpolatedSiteDescriptorContent( project, siteDescriptorContent, true );
1059
1060 decoration = readDecorationModel( siteDescriptorContent );
1061 decoration.setLastModified( siteDescriptor.lastModified() );
1062 }
1063 else
1064 {
1065 LOGGER.debug( "No" + ( depth == 0 ? "" : ( " parent level " + depth ) ) + " site descriptor." );
1066 }
1067 }
1068 catch ( IOException e )
1069 {
1070 throw new SiteToolException( "The site descriptor for " + project.getId() + " cannot be read from "
1071 + siteDescriptor, e );
1072 }
1073 finally
1074 {
1075 IOUtil.close( siteDescriptorReader );
1076 }
1077
1078
1079 MavenProject parentProject = getParentProject( project, reactorProjects, localRepository );
1080
1081
1082 if ( parentProject != null && ( decoration == null || decoration.isMergeParent() ) )
1083 {
1084 depth++;
1085 LOGGER.debug( "Looking for site descriptor of level " + depth + " parent project: "
1086 + parentProject.getId() );
1087
1088 File parentSiteDirectory = null;
1089 if ( parentProject.getBasedir() != null )
1090 {
1091
1092 String siteRelativePath = getRelativeFilePath( project.getBasedir().getAbsolutePath(),
1093 siteDescriptor.getParentFile().getAbsolutePath() );
1094
1095 parentSiteDirectory = new File( parentProject.getBasedir(), siteRelativePath );
1096
1097
1098 }
1099
1100 DecorationModel parentDecoration =
1101 getDecorationModel( depth, parentSiteDirectory, locale, parentProject, reactorProjects, localRepository,
1102 repositories ).getKey();
1103
1104
1105
1106 if ( decoration == null && parentDecoration != null )
1107 {
1108
1109 decoration = new DecorationModel();
1110 }
1111
1112 String name = project.getName();
1113 if ( decoration != null && StringUtils.isNotEmpty( decoration.getName() ) )
1114 {
1115 name = decoration.getName();
1116 }
1117
1118
1119 String projectDistMgmnt = getDistMgmntSiteUrl( project );
1120 String parentDistMgmnt = getDistMgmntSiteUrl( parentProject );
1121 if ( LOGGER.isDebugEnabled() )
1122 {
1123 LOGGER.debug( "Site decoration model inheritance: assembling child with level " + depth
1124 + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = "
1125 + parentDistMgmnt );
1126 }
1127 assembler.assembleModelInheritance( name, decoration, parentDecoration, projectDistMgmnt,
1128 parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt );
1129 }
1130
1131 return new AbstractMap.SimpleEntry<DecorationModel, MavenProject>( decoration, parentProject );
1132 }
1133
1134
1135
1136
1137
1138
1139 private DecorationModel readDecorationModel( String siteDescriptorContent )
1140 throws SiteToolException
1141 {
1142 try
1143 {
1144 return new DecorationXpp3Reader().read( new StringReader( siteDescriptorContent ) );
1145 }
1146 catch ( XmlPullParserException e )
1147 {
1148 throw new SiteToolException( "Error parsing site descriptor", e );
1149 }
1150 catch ( IOException e )
1151 {
1152 throw new SiteToolException( "Error reading site descriptor", e );
1153 }
1154 }
1155
1156 private String decorationModelToString( DecorationModel decoration )
1157 throws SiteToolException
1158 {
1159 StringWriter writer = new StringWriter();
1160
1161 try
1162 {
1163 new DecorationXpp3Writer().write( writer, decoration );
1164 return writer.toString();
1165 }
1166 catch ( IOException e )
1167 {
1168 throw new SiteToolException( "Error reading site descriptor", e );
1169 }
1170 finally
1171 {
1172 IOUtil.close( writer );
1173 }
1174 }
1175
1176 private static String buildRelativePath( final String toPath, final String fromPath, final char separatorChar )
1177 {
1178
1179 StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) );
1180 StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) );
1181
1182 int count = 0;
1183
1184
1185 while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() )
1186 {
1187 if ( separatorChar == '\\' )
1188 {
1189 if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) )
1190 {
1191 break;
1192 }
1193 }
1194 else
1195 {
1196 if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) )
1197 {
1198 break;
1199 }
1200 }
1201
1202 count++;
1203 }
1204
1205
1206
1207
1208 toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) );
1209 fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) );
1210
1211 while ( count-- > 0 )
1212 {
1213 fromTokeniser.nextToken();
1214 toTokeniser.nextToken();
1215 }
1216
1217 StringBuilder relativePath = new StringBuilder();
1218
1219
1220 while ( fromTokeniser.hasMoreTokens() )
1221 {
1222 fromTokeniser.nextToken();
1223
1224 relativePath.append( ".." );
1225
1226 if ( fromTokeniser.hasMoreTokens() )
1227 {
1228 relativePath.append( separatorChar );
1229 }
1230 }
1231
1232 if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() )
1233 {
1234 relativePath.append( separatorChar );
1235 }
1236
1237
1238 while ( toTokeniser.hasMoreTokens() )
1239 {
1240 relativePath.append( toTokeniser.nextToken() );
1241
1242 if ( toTokeniser.hasMoreTokens() )
1243 {
1244 relativePath.append( separatorChar );
1245 }
1246 }
1247 return relativePath.toString();
1248 }
1249
1250
1251
1252
1253
1254
1255
1256
1257 private void appendMenuItem( MavenProject project, Menu menu, String name, String href, String defaultHref )
1258 {
1259 String selectedHref = href;
1260
1261 if ( selectedHref == null )
1262 {
1263 selectedHref = defaultHref;
1264 }
1265
1266 MenuItem item = new MenuItem();
1267 item.setName( name );
1268
1269 String baseUrl = getDistMgmntSiteUrl( project );
1270 if ( baseUrl != null )
1271 {
1272 selectedHref = getRelativePath( selectedHref, baseUrl );
1273 }
1274
1275 if ( selectedHref.endsWith( "/" ) )
1276 {
1277 item.setHref( selectedHref + "index.html" );
1278 }
1279 else
1280 {
1281 item.setHref( selectedHref + "/index.html" );
1282 }
1283 menu.addItem( item );
1284 }
1285
1286
1287
1288
1289
1290
1291
1292
1293 private MenuItem createCategoryMenu( String name, String href, List<MavenReport> categoryReports, Locale locale )
1294 {
1295 MenuItem item = new MenuItem();
1296 item.setName( name );
1297 item.setCollapse( true );
1298 item.setHref( href );
1299
1300
1301
1302
1303 for ( MavenReport report : categoryReports )
1304 {
1305 MenuItem subitem = new MenuItem();
1306 subitem.setName( report.getName( locale ) );
1307 subitem.setHref( report.getOutputName() + ".html" );
1308 item.getItems().add( subitem );
1309 }
1310
1311 return item;
1312 }
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324 private static boolean isEmptyList( List<?> list )
1325 {
1326 return list == null || list.isEmpty();
1327 }
1328
1329
1330
1331
1332
1333
1334
1335 private static String getDistMgmntSiteUrl( MavenProject project )
1336 {
1337 return getDistMgmntSiteUrl( project.getDistributionManagement() );
1338 }
1339
1340 private static String getDistMgmntSiteUrl( DistributionManagement distMgmnt )
1341 {
1342 if ( distMgmnt != null && distMgmnt.getSite() != null && distMgmnt.getSite().getUrl() != null )
1343 {
1344 return urlEncode( distMgmnt.getSite().getUrl() );
1345 }
1346
1347 return null;
1348 }
1349
1350 private static String urlEncode( final String url )
1351 {
1352 if ( url == null )
1353 {
1354 return null;
1355 }
1356
1357 try
1358 {
1359 return new File( url ).toURI().toURL().toExternalForm();
1360 }
1361 catch ( MalformedURLException ex )
1362 {
1363 return url;
1364 }
1365 }
1366 }