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