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