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 } catch (IOException e) {
371 throw new SiteToolException("Unable to locate site descriptor", e);
372 }
373 }
374
375
376 public SiteModel getSiteModel(
377 File siteDirectory,
378 Locale locale,
379 MavenProject project,
380 List<MavenProject> reactorProjects,
381 RepositorySystemSession repoSession,
382 List<RemoteRepository> remoteProjectRepositories)
383 throws SiteToolException {
384 Objects.requireNonNull(locale, "locale cannot be null");
385 Objects.requireNonNull(project, "project cannot be null");
386 Objects.requireNonNull(reactorProjects, "reactorProjects cannot be null");
387 Objects.requireNonNull(repoSession, "repoSession cannot be null");
388 Objects.requireNonNull(remoteProjectRepositories, "remoteProjectRepositories cannot be null");
389
390 LOGGER.debug("Computing site model of '" + project.getId() + "' for "
391 + (locale.equals(SiteTool.DEFAULT_LOCALE) ? "default locale" : "locale '" + locale + "'"));
392
393 Map.Entry<SiteModel, MavenProject> result =
394 getSiteModel(0, siteDirectory, locale, project, repoSession, remoteProjectRepositories);
395 SiteModel siteModel = result.getKey();
396 MavenProject parentProject = result.getValue();
397
398 if (siteModel == null) {
399 LOGGER.debug("Using default site descriptor");
400 siteModel = getDefaultSiteModel();
401 }
402
403
404 String siteDescriptorContent = siteModelToString(siteModel);
405
406
407 siteDescriptorContent = getInterpolatedSiteDescriptorContent(project, siteDescriptorContent, false);
408
409 siteModel = readSiteModel(siteDescriptorContent);
410
411 if (parentProject != null) {
412 populateParentMenu(siteModel, locale, project, parentProject, true);
413 }
414
415 try {
416 populateModulesMenu(siteModel, locale, project, reactorProjects, true);
417 } catch (IOException e) {
418 throw new SiteToolException("Error while populating modules menu", e);
419 }
420
421 return siteModel;
422 }
423
424
425 public String getInterpolatedSiteDescriptorContent(
426 Map<String, String> props, MavenProject aProject, String siteDescriptorContent) throws SiteToolException {
427 Objects.requireNonNull(props, "props cannot be null");
428
429
430 return getInterpolatedSiteDescriptorContent(aProject, siteDescriptorContent, false);
431 }
432
433 private String getInterpolatedSiteDescriptorContent(
434 MavenProject aProject, String siteDescriptorContent, boolean isEarly) throws SiteToolException {
435 Objects.requireNonNull(aProject, "aProject cannot be null");
436 Objects.requireNonNull(siteDescriptorContent, "siteDescriptorContent cannot be null");
437
438 RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
439
440 if (isEarly) {
441 interpolator.addValueSource(new PrefixedObjectValueSource("this.", aProject));
442 interpolator.addValueSource(new PrefixedPropertiesValueSource("this.", aProject.getProperties()));
443 } else {
444 interpolator.addValueSource(new PrefixedObjectValueSource("project.", aProject));
445 interpolator.addValueSource(new MapBasedValueSource(aProject.getProperties()));
446
447 try {
448 interpolator.addValueSource(new EnvarBasedValueSource());
449 } catch (IOException e) {
450
451 throw new SiteToolException("Cannot interpolate environment properties", e);
452 }
453 }
454
455 interpolator.addPostProcessor(new InterpolationPostProcessor() {
456 @Override
457 public Object execute(String expression, Object value) {
458 if (value != null) {
459
460 return value.toString()
461 .replace("&", "&")
462 .replace("<", "<")
463 .replace(">", ">")
464 .replace("\"", """)
465 .replace("'", "'");
466 }
467 return null;
468 }
469 });
470
471 try {
472 return interpolator.interpolate(siteDescriptorContent);
473 } catch (InterpolationException e) {
474 throw new SiteToolException("Cannot interpolate site descriptor", e);
475 }
476 }
477
478
479
480
481
482
483
484
485
486
487
488 private void populateParentMenu(
489 SiteModel siteModel,
490 Locale locale,
491 MavenProject project,
492 MavenProject parentProject,
493 boolean keepInheritedRefs) {
494 Objects.requireNonNull(siteModel, "siteModel cannot be null");
495 Objects.requireNonNull(locale, "locale cannot be null");
496 Objects.requireNonNull(project, "project cannot be null");
497 Objects.requireNonNull(parentProject, "parentProject cannot be null");
498
499 Menu menu = siteModel.getMenuRef("parent");
500
501 if (menu == null) {
502 return;
503 }
504
505 if (keepInheritedRefs && menu.isInheritAsRef()) {
506 return;
507 }
508
509 String parentUrl = getDistMgmntSiteUrl(parentProject);
510
511 if (parentUrl != null) {
512 if (parentUrl.endsWith("/")) {
513 parentUrl += "index.html";
514 } else {
515 parentUrl += "/index.html";
516 }
517
518 parentUrl = getRelativePath(parentUrl, getDistMgmntSiteUrl(project));
519 } else {
520
521 File parentBasedir = parentProject.getBasedir();
522
523 if (parentBasedir != null) {
524
525 String parentPath = parentBasedir.getAbsolutePath();
526 String projectPath = project.getBasedir().getAbsolutePath();
527 parentUrl = getRelativePath(parentPath, projectPath) + "/index.html";
528 }
529 }
530
531
532 if (parentUrl == null) {
533 LOGGER.warn("Unable to find a URL to the parent project. The parent menu will NOT be added.");
534 } else {
535 if (menu.getName() == null) {
536 menu.setName(i18n.getString("site-tool", locale, "siteModel.menu.parentproject"));
537 }
538
539 MenuItem item = new MenuItem();
540 item.setName(parentProject.getName());
541 item.setHref(parentUrl);
542 menu.addItem(item);
543 }
544 }
545
546
547
548
549
550
551
552
553
554
555
556
557
558 private void populateModulesMenu(
559 SiteModel siteModel,
560 Locale locale,
561 MavenProject project,
562 List<MavenProject> reactorProjects,
563 boolean keepInheritedRefs)
564 throws SiteToolException, IOException {
565 Objects.requireNonNull(siteModel, "siteModel cannot be null");
566 Objects.requireNonNull(locale, "locale cannot be null");
567 Objects.requireNonNull(project, "project cannot be null");
568 Objects.requireNonNull(reactorProjects, "reactorProjects cannot be null");
569
570 Menu menu = siteModel.getMenuRef("modules");
571
572 if (menu == null) {
573 return;
574 }
575
576 if (keepInheritedRefs && menu.isInheritAsRef()) {
577 return;
578 }
579
580
581 if (!project.getModules().isEmpty()) {
582 if (menu.getName() == null) {
583 menu.setName(i18n.getString("site-tool", locale, "siteModel.menu.projectmodules"));
584 }
585
586 for (String module : project.getModules()) {
587 MavenProject moduleProject = getModuleFromReactor(project, reactorProjects, module);
588
589 if (moduleProject == null) {
590 LOGGER.debug("Module " + module + " not found in reactor");
591 continue;
592 }
593
594 final String pluginId = "org.apache.maven.plugins:maven-site-plugin";
595 String skipFlag = getPluginParameter(moduleProject, pluginId, "skip");
596 if (skipFlag == null) {
597 skipFlag = moduleProject.getProperties().getProperty("maven.site.skip");
598 }
599
600 String siteUrl = "true".equalsIgnoreCase(skipFlag) ? null : getDistMgmntSiteUrl(moduleProject);
601 String itemName =
602 (moduleProject.getName() == null) ? moduleProject.getArtifactId() : moduleProject.getName();
603 String defaultSiteUrl = "true".equalsIgnoreCase(skipFlag) ? null : moduleProject.getArtifactId();
604
605 appendMenuItem(project, menu, itemName, siteUrl, defaultSiteUrl);
606 }
607 } else if (siteModel.getMenuRef("modules").getInherit() == null) {
608
609 siteModel.removeMenuRef("modules");
610 }
611 }
612
613 private MavenProject getModuleFromReactor(MavenProject project, List<MavenProject> reactorProjects, String module)
614 throws IOException {
615 File moduleBasedir = new File(project.getBasedir(), module).getCanonicalFile();
616
617 for (MavenProject reactorProject : reactorProjects) {
618 if (moduleBasedir.equals(reactorProject.getBasedir())) {
619 return reactorProject;
620 }
621 }
622
623
624 return null;
625 }
626
627
628 public void populateReportsMenu(SiteModel siteModel, Locale locale, Map<String, List<MavenReport>> categories) {
629 Objects.requireNonNull(siteModel, "siteModel cannot be null");
630 Objects.requireNonNull(locale, "locale cannot be null");
631 Objects.requireNonNull(categories, "categories cannot be null");
632
633 Menu menu = siteModel.getMenuRef("reports");
634
635 if (menu == null) {
636 return;
637 }
638
639 if (menu.getName() == null) {
640 menu.setName(i18n.getString("site-tool", locale, "siteModel.menu.projectdocumentation"));
641 }
642
643 boolean found = false;
644 if (menu.getItems().isEmpty()) {
645 List<MavenReport> categoryReports = categories.get(MavenReport.CATEGORY_PROJECT_INFORMATION);
646 if (!isEmptyList(categoryReports)) {
647 MenuItem item = createCategoryMenu(
648 i18n.getString("site-tool", locale, "siteModel.menu.projectinformation"),
649 "/project-info.html",
650 categoryReports,
651 locale);
652 menu.getItems().add(item);
653 found = true;
654 }
655
656 categoryReports = categories.get(MavenReport.CATEGORY_PROJECT_REPORTS);
657 if (!isEmptyList(categoryReports)) {
658 MenuItem item = createCategoryMenu(
659 i18n.getString("site-tool", locale, "siteModel.menu.projectreports"),
660 "/project-reports.html",
661 categoryReports,
662 locale);
663 menu.getItems().add(item);
664 found = true;
665 }
666 }
667 if (!found) {
668 siteModel.removeMenuRef("reports");
669 }
670 }
671
672
673 public List<Locale> getSiteLocales(String locales) {
674 if (locales == null) {
675 return Collections.singletonList(DEFAULT_LOCALE);
676 }
677
678 String[] localesArray = StringUtils.split(locales, ",");
679 List<Locale> localesList = new ArrayList<Locale>(localesArray.length);
680 List<Locale> availableLocales = Arrays.asList(Locale.getAvailableLocales());
681
682 for (String localeString : localesArray) {
683 Locale locale = codeToLocale(localeString);
684
685 if (locale == null) {
686 continue;
687 }
688
689 if (!availableLocales.contains(locale)) {
690 if (LOGGER.isWarnEnabled()) {
691 LOGGER.warn("The locale defined by '" + locale
692 + "' is not available in this Java Virtual Machine ("
693 + System.getProperty("java.version")
694 + " from " + System.getProperty("java.vendor") + ") - IGNORING");
695 }
696 continue;
697 }
698
699 Locale bundleLocale = i18n.getBundle("site-tool", locale).getLocale();
700 if (!(bundleLocale.equals(locale) || bundleLocale.getLanguage().equals(locale.getLanguage()))) {
701 if (LOGGER.isWarnEnabled()) {
702 LOGGER.warn("The locale '" + locale + "' (" + locale.getDisplayName(Locale.ENGLISH)
703 + ") is not currently supported by Maven Site - IGNORING."
704 + System.lineSeparator() + "Contributions are welcome and greatly appreciated!"
705 + System.lineSeparator() + "If you want to contribute a new translation, please visit "
706 + "https://maven.apache.org/plugins/localization.html for detailed instructions.");
707 }
708
709 continue;
710 }
711
712 localesList.add(locale);
713 }
714
715 if (localesList.isEmpty()) {
716 localesList = Collections.singletonList(DEFAULT_LOCALE);
717 }
718
719 return localesList;
720 }
721
722
723
724
725
726
727
728
729
730
731
732
733
734 private Locale codeToLocale(String localeCode) {
735 if (localeCode == null) {
736 return null;
737 }
738
739 if ("system".equalsIgnoreCase(localeCode)) {
740 return Locale.getDefault();
741 }
742
743 if ("default".equalsIgnoreCase(localeCode)) {
744 return SiteTool.DEFAULT_LOCALE;
745 }
746
747 String language = "";
748 String country = "";
749 String variant = "";
750
751 StringTokenizer tokenizer = new StringTokenizer(localeCode, "_");
752 final int maxTokens = 3;
753 if (tokenizer.countTokens() > maxTokens) {
754 if (LOGGER.isWarnEnabled()) {
755 LOGGER.warn("Invalid java.util.Locale format for '" + localeCode + "' entry - IGNORING");
756 }
757 return null;
758 }
759
760 if (tokenizer.hasMoreTokens()) {
761 language = tokenizer.nextToken();
762 if (tokenizer.hasMoreTokens()) {
763 country = tokenizer.nextToken();
764 if (tokenizer.hasMoreTokens()) {
765 variant = tokenizer.nextToken();
766 }
767 }
768 }
769
770 return new Locale(language, country, variant);
771 }
772
773
774
775
776
777
778
779
780
781
782 protected static String getNormalizedPath(String path) {
783 String normalized = FilenameUtils.normalize(path);
784 if (normalized == null) {
785 normalized = path;
786 }
787 return (normalized == null) ? null : normalized.replace('\\', '/');
788 }
789
790
791
792
793
794
795
796
797
798
799
800 private ArtifactRequest createSiteDescriptorArtifactRequest(
801 MavenProject project, String localeStr, List<RemoteRepository> remoteProjectRepositories) {
802 String type = "xml";
803 ArtifactHandler artifactHandler = artifactHandlerManager.getArtifactHandler(type);
804 Artifact artifact = new DefaultArtifact(
805 project.getGroupId(),
806 project.getArtifactId(),
807 project.getVersion(),
808 Artifact.SCOPE_RUNTIME,
809 type,
810 "site" + (localeStr.isEmpty() ? "" : "_" + localeStr),
811 artifactHandler);
812 return new ArtifactRequest(
813 RepositoryUtils.toArtifact(artifact), remoteProjectRepositories, "remote-site-descriptor");
814 }
815
816
817
818
819
820
821
822
823
824
825 private File resolveSiteDescriptor(
826 MavenProject project,
827 RepositorySystemSession repoSession,
828 List<RemoteRepository> remoteProjectRepositories,
829 Locale locale)
830 throws IOException, ArtifactResolutionException {
831 String variant = locale.getVariant();
832 String country = locale.getCountry();
833 String language = locale.getLanguage();
834
835 String localeStr = null;
836 File siteDescriptor = null;
837 boolean found = false;
838
839 if (!variant.isEmpty()) {
840 localeStr = language + "_" + country + "_" + variant;
841 ArtifactRequest request =
842 createSiteDescriptorArtifactRequest(project, localeStr, remoteProjectRepositories);
843
844 deletePseudoSiteDescriptorMarkerFile(repoSession, request);
845
846 try {
847 ArtifactResult result = repositorySystem.resolveArtifact(repoSession, request);
848
849 siteDescriptor = result.getArtifact().getFile();
850 found = true;
851 } catch (ArtifactResolutionException e) {
852
853 if (e.getResult().getExceptions().stream().anyMatch(re -> re instanceof ArtifactNotFoundException)) {
854 LOGGER.debug("No site descriptor found for '" + project.getId() + "' for locale '" + localeStr
855 + "', trying without variant...");
856 } else {
857 throw e;
858 }
859 }
860 }
861
862 if (!found && !country.isEmpty()) {
863 localeStr = language + "_" + country;
864 ArtifactRequest request =
865 createSiteDescriptorArtifactRequest(project, localeStr, remoteProjectRepositories);
866
867 deletePseudoSiteDescriptorMarkerFile(repoSession, request);
868
869 try {
870 ArtifactResult result = repositorySystem.resolveArtifact(repoSession, request);
871
872 siteDescriptor = result.getArtifact().getFile();
873 found = true;
874 } catch (ArtifactResolutionException e) {
875
876 if (e.getResult().getExceptions().stream().anyMatch(re -> re instanceof ArtifactNotFoundException)) {
877 LOGGER.debug("No site descriptor found for '" + project.getId() + "' for locale '" + localeStr
878 + "', trying without country...");
879 } else {
880 throw e;
881 }
882 }
883 }
884
885 if (!found && !language.isEmpty()) {
886 localeStr = language;
887 ArtifactRequest request =
888 createSiteDescriptorArtifactRequest(project, localeStr, remoteProjectRepositories);
889
890 deletePseudoSiteDescriptorMarkerFile(repoSession, request);
891
892 try {
893 ArtifactResult result = repositorySystem.resolveArtifact(repoSession, request);
894
895 siteDescriptor = result.getArtifact().getFile();
896 found = true;
897 } catch (ArtifactResolutionException e) {
898
899 if (e.getResult().getExceptions().stream().anyMatch(re -> re instanceof ArtifactNotFoundException)) {
900 LOGGER.debug("No site descriptor found for '" + project.getId() + "' for locale '" + localeStr
901 + "', trying without language (default locale)...");
902 } else {
903 throw e;
904 }
905 }
906 }
907
908 if (!found) {
909 localeStr = SiteTool.DEFAULT_LOCALE.toString();
910 ArtifactRequest request =
911 createSiteDescriptorArtifactRequest(project, localeStr, remoteProjectRepositories);
912
913 deletePseudoSiteDescriptorMarkerFile(repoSession, request);
914
915 try {
916 ArtifactResult result = repositorySystem.resolveArtifact(repoSession, request);
917
918 siteDescriptor = result.getArtifact().getFile();
919 } catch (ArtifactResolutionException e) {
920
921 if (e.getResult().getExceptions().stream().anyMatch(re -> re instanceof ArtifactNotFoundException)) {
922 LOGGER.debug("No site descriptor found for '" + project.getId() + "' with default locale");
923 return null;
924 }
925
926 throw e;
927 }
928 }
929
930 return siteDescriptor;
931 }
932
933
934 private void deletePseudoSiteDescriptorMarkerFile(RepositorySystemSession repoSession, ArtifactRequest request) {
935 LocalRepositoryManager lrm = repoSession.getLocalRepositoryManager();
936
937 LocalArtifactRequest localRequest = new LocalArtifactRequest();
938 localRequest.setArtifact(request.getArtifact());
939
940 LocalArtifactResult localResult = lrm.find(repoSession, localRequest);
941 File localArtifactFile = localResult.getFile();
942
943 try {
944 if (localResult.isAvailable() && Files.size(localArtifactFile.toPath()) == 0L) {
945 LOGGER.debug(
946 "Deleting 0-byte pseudo marker file for artifact '{}' at '{}'",
947 localRequest.getArtifact(),
948 localArtifactFile);
949 Files.delete(localArtifactFile.toPath());
950 }
951 } catch (IOException e) {
952 LOGGER.debug("Failed to delete 0-byte pseudo marker file for artifact '{}'", localRequest.getArtifact(), e);
953 }
954 }
955
956
957
958
959
960
961
962
963
964
965
966 private Map.Entry<SiteModel, MavenProject> getSiteModel(
967 int depth,
968 File siteDirectory,
969 Locale locale,
970 MavenProject project,
971 RepositorySystemSession repoSession,
972 List<RemoteRepository> remoteProjectRepositories)
973 throws SiteToolException {
974
975 File siteDescriptor;
976 if (project.getBasedir() == null) {
977
978 try {
979 siteDescriptor =
980 getSiteDescriptorFromRepository(project, repoSession, remoteProjectRepositories, locale);
981 } catch (SiteToolException e) {
982 throw new SiteToolException("The site descriptor cannot be resolved from the repository", e);
983 }
984 } else {
985
986 siteDescriptor = getSiteDescriptor(siteDirectory, locale);
987 }
988
989
990 SiteModel siteModel = null;
991 Reader siteDescriptorReader = null;
992 try {
993 if (siteDescriptor != null && siteDescriptor.exists()) {
994 LOGGER.debug("Reading" + (depth == 0 ? "" : (" parent level " + depth)) + " site descriptor from "
995 + siteDescriptor);
996
997 siteDescriptorReader = ReaderFactory.newXmlReader(siteDescriptor);
998
999 String siteDescriptorContent = IOUtil.toString(siteDescriptorReader);
1000
1001
1002 siteDescriptorContent = getInterpolatedSiteDescriptorContent(project, siteDescriptorContent, true);
1003
1004 siteModel = readSiteModel(siteDescriptorContent);
1005 siteModel.setLastModified(siteDescriptor.lastModified());
1006 } else {
1007 LOGGER.debug("No" + (depth == 0 ? "" : (" parent level " + depth)) + " site descriptor");
1008 }
1009 } catch (IOException e) {
1010 throw new SiteToolException(
1011 "The site descriptor for '" + project.getId() + "' cannot be read from " + siteDescriptor, e);
1012 } finally {
1013 IOUtil.close(siteDescriptorReader);
1014 }
1015
1016
1017 MavenProject parentProject = project.getParent();
1018
1019
1020 if (parentProject != null && (siteModel == null || siteModel.isMergeParent())) {
1021 depth++;
1022 LOGGER.debug("Looking for site descriptor of level " + depth + " parent project: " + parentProject.getId());
1023
1024 File parentSiteDirectory = null;
1025 if (parentProject.getBasedir() != null) {
1026
1027 String siteRelativePath = getRelativeFilePath(
1028 project.getBasedir().getAbsolutePath(),
1029 siteDescriptor.getParentFile().getAbsolutePath());
1030
1031 parentSiteDirectory = new File(parentProject.getBasedir(), siteRelativePath);
1032
1033
1034 }
1035
1036 SiteModel parentSiteModel = getSiteModel(
1037 depth, parentSiteDirectory, locale, parentProject, repoSession, remoteProjectRepositories)
1038 .getKey();
1039
1040
1041
1042 if (siteModel == null && parentSiteModel != null) {
1043
1044
1045 siteModel = new SiteModel();
1046 }
1047
1048 String name = project.getName();
1049 if (siteModel != null && StringUtils.isNotEmpty(siteModel.getName())) {
1050 name = siteModel.getName();
1051 }
1052
1053
1054 String projectDistMgmnt = getDistMgmntSiteUrl(project);
1055 String parentDistMgmnt = getDistMgmntSiteUrl(parentProject);
1056 if (LOGGER.isDebugEnabled()) {
1057 LOGGER.debug("Site model inheritance: assembling child with level " + depth
1058 + " parent: distributionManagement.site.url child = " + projectDistMgmnt + " and parent = "
1059 + parentDistMgmnt);
1060 }
1061 assembler.assembleModelInheritance(
1062 name,
1063 siteModel,
1064 parentSiteModel,
1065 projectDistMgmnt,
1066 parentDistMgmnt == null ? projectDistMgmnt : parentDistMgmnt);
1067 }
1068
1069 return new AbstractMap.SimpleEntry<SiteModel, MavenProject>(siteModel, parentProject);
1070 }
1071
1072
1073
1074
1075
1076
1077 private SiteModel readSiteModel(String siteDescriptorContent) throws SiteToolException {
1078 try {
1079 return new SiteXpp3Reader().read(new StringReader(siteDescriptorContent));
1080 } catch (XmlPullParserException e) {
1081 throw new SiteToolException("Error parsing site descriptor", e);
1082 } catch (IOException e) {
1083 throw new SiteToolException("Error reading site descriptor", e);
1084 }
1085 }
1086
1087 private SiteModel getDefaultSiteModel() throws SiteToolException {
1088 String siteDescriptorContent;
1089
1090 Reader reader = null;
1091 try {
1092 reader = ReaderFactory.newXmlReader(getClass().getResourceAsStream("/default-site.xml"));
1093 siteDescriptorContent = IOUtil.toString(reader);
1094 } catch (IOException e) {
1095 throw new SiteToolException("Error reading default site descriptor", e);
1096 } finally {
1097 IOUtil.close(reader);
1098 }
1099
1100 return readSiteModel(siteDescriptorContent);
1101 }
1102
1103 private String siteModelToString(SiteModel siteModel) throws SiteToolException {
1104 StringWriter writer = new StringWriter();
1105
1106 try {
1107 new SiteXpp3Writer().write(writer, siteModel);
1108 return writer.toString();
1109 } catch (IOException e) {
1110 throw new SiteToolException("Error reading site descriptor", e);
1111 } finally {
1112 IOUtil.close(writer);
1113 }
1114 }
1115
1116 private static String buildRelativePath(final String toPath, final String fromPath, final char separatorChar) {
1117
1118 StringTokenizer toTokeniser = new StringTokenizer(toPath, String.valueOf(separatorChar));
1119 StringTokenizer fromTokeniser = new StringTokenizer(fromPath, String.valueOf(separatorChar));
1120
1121 int count = 0;
1122
1123
1124 while (toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens()) {
1125 if (separatorChar == '\\') {
1126 if (!fromTokeniser.nextToken().equalsIgnoreCase(toTokeniser.nextToken())) {
1127 break;
1128 }
1129 } else {
1130 if (!fromTokeniser.nextToken().equals(toTokeniser.nextToken())) {
1131 break;
1132 }
1133 }
1134
1135 count++;
1136 }
1137
1138
1139
1140
1141 toTokeniser = new StringTokenizer(toPath, String.valueOf(separatorChar));
1142 fromTokeniser = new StringTokenizer(fromPath, String.valueOf(separatorChar));
1143
1144 while (count-- > 0) {
1145 fromTokeniser.nextToken();
1146 toTokeniser.nextToken();
1147 }
1148
1149 StringBuilder relativePath = new StringBuilder();
1150
1151
1152 while (fromTokeniser.hasMoreTokens()) {
1153 fromTokeniser.nextToken();
1154
1155 relativePath.append("..");
1156
1157 if (fromTokeniser.hasMoreTokens()) {
1158 relativePath.append(separatorChar);
1159 }
1160 }
1161
1162 if (relativePath.length() != 0 && toTokeniser.hasMoreTokens()) {
1163 relativePath.append(separatorChar);
1164 }
1165
1166
1167 while (toTokeniser.hasMoreTokens()) {
1168 relativePath.append(toTokeniser.nextToken());
1169
1170 if (toTokeniser.hasMoreTokens()) {
1171 relativePath.append(separatorChar);
1172 }
1173 }
1174 return relativePath.toString();
1175 }
1176
1177
1178
1179
1180
1181
1182
1183
1184 private void appendMenuItem(MavenProject project, Menu menu, String name, String href, String defaultHref) {
1185 String selectedHref = href;
1186
1187 if (selectedHref == null) {
1188 selectedHref = defaultHref;
1189 }
1190
1191 MenuItem item = new MenuItem();
1192 item.setName(name);
1193
1194 if (selectedHref != null) {
1195 String baseUrl = getDistMgmntSiteUrl(project);
1196 if (baseUrl != null) {
1197 selectedHref = getRelativePath(selectedHref, baseUrl);
1198 }
1199
1200 if (selectedHref.endsWith("/")) {
1201 item.setHref(selectedHref + "index.html");
1202 } else {
1203 item.setHref(selectedHref + "/index.html");
1204 }
1205 }
1206 menu.addItem(item);
1207 }
1208
1209
1210
1211
1212
1213
1214
1215
1216 private MenuItem createCategoryMenu(String name, String href, List<MavenReport> categoryReports, Locale locale) {
1217 MenuItem item = new MenuItem();
1218 item.setName(name);
1219 item.setCollapse(true);
1220 item.setHref(href);
1221
1222
1223
1224
1225 for (MavenReport report : categoryReports) {
1226 MenuItem subitem = new MenuItem();
1227 subitem.setName(report.getName(locale));
1228 subitem.setHref(report.getOutputName() + ".html");
1229 item.getItems().add(subitem);
1230 }
1231
1232 return item;
1233 }
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245 private static boolean isEmptyList(List<?> list) {
1246 return list == null || list.isEmpty();
1247 }
1248
1249
1250
1251
1252
1253
1254
1255 private static String getDistMgmntSiteUrl(MavenProject project) {
1256 return getDistMgmntSiteUrl(project.getDistributionManagement());
1257 }
1258
1259 private static String getDistMgmntSiteUrl(DistributionManagement distMgmnt) {
1260 if (distMgmnt != null
1261 && distMgmnt.getSite() != null
1262 && distMgmnt.getSite().getUrl() != null) {
1263
1264 return urlEncode(distMgmnt.getSite().getUrl());
1265 }
1266
1267 return null;
1268 }
1269
1270
1271
1272
1273
1274
1275 private static Plugin getPlugin(MavenProject project, String pluginId) {
1276 if ((project.getBuild() == null) || (project.getBuild().getPluginsAsMap() == null)) {
1277 return null;
1278 }
1279
1280 Plugin plugin = project.getBuild().getPluginsAsMap().get(pluginId);
1281
1282 if ((plugin == null)
1283 && (project.getBuild().getPluginManagement() != null)
1284 && (project.getBuild().getPluginManagement().getPluginsAsMap() != null)) {
1285 plugin = project.getBuild().getPluginManagement().getPluginsAsMap().get(pluginId);
1286 }
1287
1288 return plugin;
1289 }
1290
1291
1292
1293
1294
1295
1296
1297 private static String getPluginParameter(MavenProject project, String pluginId, String param) {
1298 Plugin plugin = getPlugin(project, pluginId);
1299 if (plugin != null) {
1300 Xpp3Dom xpp3Dom = (Xpp3Dom) plugin.getConfiguration();
1301 if (xpp3Dom != null
1302 && xpp3Dom.getChild(param) != null
1303 && StringUtils.isNotEmpty(xpp3Dom.getChild(param).getValue())) {
1304 return xpp3Dom.getChild(param).getValue();
1305 }
1306 }
1307
1308 return null;
1309 }
1310
1311 private static String urlEncode(final String url) {
1312 if (url == null) {
1313 return null;
1314 }
1315
1316 try {
1317 return new File(url).toURI().toURL().toExternalForm();
1318 } catch (MalformedURLException ex) {
1319 return url;
1320 }
1321 }
1322 }