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